ropilot 0.1.2
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 +76 -0
- package/bin/ropilot.js +70 -0
- package/lib/config.js +119 -0
- package/lib/index.js +7 -0
- package/lib/proxy.js +209 -0
- package/lib/setup.js +258 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Ropilot CLI
|
|
2
|
+
|
|
3
|
+
AI-powered Roblox development assistant - MCP CLI wrapper.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run directly with npx (recommended)
|
|
9
|
+
npx ropilot init
|
|
10
|
+
|
|
11
|
+
# Or install globally
|
|
12
|
+
npm install -g ropilot
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
1. **Initialize Ropilot in your project:**
|
|
18
|
+
```bash
|
|
19
|
+
npx ropilot init
|
|
20
|
+
```
|
|
21
|
+
This will:
|
|
22
|
+
- Prompt for your API key (get one at https://ropilot.ai)
|
|
23
|
+
- Download the latest agent prompts and configs
|
|
24
|
+
- Set up `.claude/` directory with subagent definitions
|
|
25
|
+
|
|
26
|
+
2. **Add to your AI IDE:**
|
|
27
|
+
|
|
28
|
+
**Claude Desktop** - Add to `claude_desktop_config.json`:
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"ropilot": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["ropilot"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Or use HTTP directly:**
|
|
41
|
+
```
|
|
42
|
+
https://ropilot.ai/ropilot/mcp/YOUR_API_KEY/message
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. **Make sure Roblox Studio is running** with the Ropilot plugin installed.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx ropilot init [key] # Set up Ropilot in current project
|
|
51
|
+
npx ropilot # Run as stdio MCP server
|
|
52
|
+
npx ropilot config # Show current configuration
|
|
53
|
+
npx ropilot update # Update prompts to latest version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
The CLI acts as a thin wrapper around the Ropilot edge server:
|
|
59
|
+
|
|
60
|
+
1. **On `init`**: Downloads agent prompts and configs from the edge server, sets up your project
|
|
61
|
+
2. **On `serve`** (default): Runs as a stdio MCP server, proxying all requests to the edge server
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
Global config is stored in `~/.ropilot/config.json`.
|
|
66
|
+
|
|
67
|
+
Project-specific config can be added in `.ropilot.json`:
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"apiKey": "ropilot_..."
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- Website: https://ropilot.ai
|
package/bin/ropilot.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ropilot CLI
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* ropilot init [key] - Set up Ropilot in current project (downloads prompts, creates configs)
|
|
8
|
+
* ropilot - Run as stdio MCP server (proxies to edge)
|
|
9
|
+
* ropilot config - Show current configuration
|
|
10
|
+
* ropilot update - Update prompts and configs to latest version
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { dirname, join } from 'path';
|
|
15
|
+
import { existsSync, readFileSync } from 'fs';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
const libDir = join(__dirname, '..', 'lib');
|
|
20
|
+
|
|
21
|
+
// Parse command line args
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const command = args[0] || 'serve';
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
switch (command) {
|
|
27
|
+
case 'init': {
|
|
28
|
+
const { init } = await import(join(libDir, 'setup.js'));
|
|
29
|
+
const apiKey = args[1] || null;
|
|
30
|
+
await init(apiKey);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case 'update': {
|
|
35
|
+
const { update } = await import(join(libDir, 'setup.js'));
|
|
36
|
+
await update();
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
case 'config': {
|
|
41
|
+
const { showConfig } = await import(join(libDir, 'config.js'));
|
|
42
|
+
await showConfig();
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case 'serve':
|
|
47
|
+
default: {
|
|
48
|
+
// If first arg looks like a command but isn't recognized, show help
|
|
49
|
+
if (command && !command.startsWith('-') && !['serve', 'init', 'update', 'config'].includes(command)) {
|
|
50
|
+
console.error(`Unknown command: ${command}`);
|
|
51
|
+
console.error('');
|
|
52
|
+
console.error('Usage:');
|
|
53
|
+
console.error(' ropilot init [key] - Set up Ropilot in current project');
|
|
54
|
+
console.error(' ropilot - Run as stdio MCP server');
|
|
55
|
+
console.error(' ropilot config - Show current configuration');
|
|
56
|
+
console.error(' ropilot update - Update prompts to latest version');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { serve } = await import(join(libDir, 'proxy.js'));
|
|
61
|
+
await serve();
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch(err => {
|
|
68
|
+
console.error('Error:', err.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Ropilot CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
|
|
9
|
+
// Global config directory (~/.ropilot/)
|
|
10
|
+
const GLOBAL_CONFIG_DIR = join(homedir(), '.ropilot');
|
|
11
|
+
const GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, 'config.json');
|
|
12
|
+
|
|
13
|
+
// Project-level config file
|
|
14
|
+
const PROJECT_CONFIG_FILE = '.ropilot.json';
|
|
15
|
+
|
|
16
|
+
// Edge server base URL
|
|
17
|
+
export const EDGE_URL = 'https://ropilot.ai/ropilot';
|
|
18
|
+
|
|
19
|
+
// Default config
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
apiKey: null,
|
|
22
|
+
promptsVersion: null,
|
|
23
|
+
lastUpdated: null
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Ensure global config directory exists
|
|
28
|
+
*/
|
|
29
|
+
export function ensureConfigDir() {
|
|
30
|
+
if (!existsSync(GLOBAL_CONFIG_DIR)) {
|
|
31
|
+
mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load global config
|
|
37
|
+
*/
|
|
38
|
+
export function loadGlobalConfig() {
|
|
39
|
+
ensureConfigDir();
|
|
40
|
+
|
|
41
|
+
if (existsSync(GLOBAL_CONFIG_FILE)) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(GLOBAL_CONFIG_FILE, 'utf-8'));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return { ...DEFAULT_CONFIG };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { ...DEFAULT_CONFIG };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Save global config
|
|
54
|
+
*/
|
|
55
|
+
export function saveGlobalConfig(config) {
|
|
56
|
+
ensureConfigDir();
|
|
57
|
+
writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load project config (if exists)
|
|
62
|
+
*/
|
|
63
|
+
export function loadProjectConfig() {
|
|
64
|
+
if (existsSync(PROJECT_CONFIG_FILE)) {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(readFileSync(PROJECT_CONFIG_FILE, 'utf-8'));
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the API key (project config overrides global)
|
|
76
|
+
*/
|
|
77
|
+
export function getApiKey() {
|
|
78
|
+
const projectConfig = loadProjectConfig();
|
|
79
|
+
if (projectConfig?.apiKey) {
|
|
80
|
+
return projectConfig.apiKey;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const globalConfig = loadGlobalConfig();
|
|
84
|
+
return globalConfig.apiKey;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Set the API key in global config
|
|
89
|
+
*/
|
|
90
|
+
export function setApiKey(apiKey) {
|
|
91
|
+
const config = loadGlobalConfig();
|
|
92
|
+
config.apiKey = apiKey;
|
|
93
|
+
saveGlobalConfig(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Show current configuration
|
|
98
|
+
*/
|
|
99
|
+
export async function showConfig() {
|
|
100
|
+
const globalConfig = loadGlobalConfig();
|
|
101
|
+
const projectConfig = loadProjectConfig();
|
|
102
|
+
|
|
103
|
+
console.log('Ropilot Configuration');
|
|
104
|
+
console.log('=====================');
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log('Global config (~/.ropilot/config.json):');
|
|
107
|
+
console.log(` API Key: ${globalConfig.apiKey ? globalConfig.apiKey.slice(0, 20) + '...' : '(not set)'}`);
|
|
108
|
+
console.log(` Prompts Version: ${globalConfig.promptsVersion || '(not downloaded)'}`);
|
|
109
|
+
console.log(` Last Updated: ${globalConfig.lastUpdated || 'never'}`);
|
|
110
|
+
console.log('');
|
|
111
|
+
|
|
112
|
+
if (projectConfig) {
|
|
113
|
+
console.log('Project config (.ropilot.json):');
|
|
114
|
+
console.log(` API Key: ${projectConfig.apiKey ? projectConfig.apiKey.slice(0, 20) + '...' : '(not set)'}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(`Edge Server: ${EDGE_URL}`);
|
|
119
|
+
}
|
package/lib/index.js
ADDED
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio to HTTP proxy for Ropilot MCP
|
|
3
|
+
*
|
|
4
|
+
* This module implements a stdio MCP server that proxies
|
|
5
|
+
* all requests to the Ropilot edge server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createInterface } from 'readline';
|
|
9
|
+
import { getApiKey, EDGE_URL } from './config.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Send JSON-RPC response to stdout
|
|
13
|
+
*/
|
|
14
|
+
function sendResponse(response) {
|
|
15
|
+
const json = JSON.stringify(response);
|
|
16
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Send JSON-RPC error response
|
|
21
|
+
*/
|
|
22
|
+
function sendError(id, code, message) {
|
|
23
|
+
sendResponse({
|
|
24
|
+
jsonrpc: '2.0',
|
|
25
|
+
id,
|
|
26
|
+
error: { code, message }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Forward request to edge server
|
|
32
|
+
*/
|
|
33
|
+
async function forwardToEdge(apiKey, request) {
|
|
34
|
+
const url = `${EDGE_URL}/mcp/${apiKey}/message`;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(request)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
throw new Error(`Edge server error: ${response.status} ${text}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return await response.json();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
throw new Error(`Failed to connect to edge server: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handle MCP initialize request
|
|
58
|
+
*/
|
|
59
|
+
function handleInitialize(request) {
|
|
60
|
+
return {
|
|
61
|
+
jsonrpc: '2.0',
|
|
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
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handle MCP tools/list request
|
|
78
|
+
*/
|
|
79
|
+
async function handleToolsList(apiKey, request) {
|
|
80
|
+
// Forward to edge to get actual tool list
|
|
81
|
+
return await forwardToEdge(apiKey, request);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle MCP tools/call request
|
|
86
|
+
*/
|
|
87
|
+
async function handleToolsCall(apiKey, request) {
|
|
88
|
+
// Forward to edge
|
|
89
|
+
return await forwardToEdge(apiKey, request);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Process a single MCP request
|
|
94
|
+
*/
|
|
95
|
+
async function processRequest(apiKey, request) {
|
|
96
|
+
const method = request.method;
|
|
97
|
+
|
|
98
|
+
switch (method) {
|
|
99
|
+
case 'initialize':
|
|
100
|
+
return handleInitialize(request);
|
|
101
|
+
|
|
102
|
+
case 'initialized':
|
|
103
|
+
// Notification, no response needed
|
|
104
|
+
return null;
|
|
105
|
+
|
|
106
|
+
case 'tools/list':
|
|
107
|
+
return await handleToolsList(apiKey, request);
|
|
108
|
+
|
|
109
|
+
case 'tools/call':
|
|
110
|
+
return await handleToolsCall(apiKey, request);
|
|
111
|
+
|
|
112
|
+
default:
|
|
113
|
+
// Forward unknown methods to edge
|
|
114
|
+
return await forwardToEdge(apiKey, request);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Run the stdio MCP server
|
|
120
|
+
*/
|
|
121
|
+
export async function serve() {
|
|
122
|
+
const apiKey = getApiKey();
|
|
123
|
+
|
|
124
|
+
if (!apiKey) {
|
|
125
|
+
console.error('No API key configured.');
|
|
126
|
+
console.error('Run "npx ropilot init" to set up Ropilot.');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Read JSON-RPC messages from stdin
|
|
131
|
+
let buffer = '';
|
|
132
|
+
let contentLength = -1;
|
|
133
|
+
|
|
134
|
+
const rl = createInterface({
|
|
135
|
+
input: process.stdin,
|
|
136
|
+
terminal: false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Handle line-based input (for simpler clients)
|
|
140
|
+
process.stdin.on('data', (chunk) => {
|
|
141
|
+
buffer += chunk.toString();
|
|
142
|
+
|
|
143
|
+
// Try to parse Content-Length header
|
|
144
|
+
while (true) {
|
|
145
|
+
if (contentLength === -1) {
|
|
146
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
147
|
+
if (headerEnd === -1) break;
|
|
148
|
+
|
|
149
|
+
const header = buffer.slice(0, headerEnd);
|
|
150
|
+
const match = header.match(/Content-Length: (\d+)/i);
|
|
151
|
+
if (match) {
|
|
152
|
+
contentLength = parseInt(match[1], 10);
|
|
153
|
+
buffer = buffer.slice(headerEnd + 4);
|
|
154
|
+
} else {
|
|
155
|
+
// No Content-Length, try to parse as raw JSON
|
|
156
|
+
try {
|
|
157
|
+
const lineEnd = buffer.indexOf('\n');
|
|
158
|
+
if (lineEnd === -1) break;
|
|
159
|
+
|
|
160
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
161
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
162
|
+
|
|
163
|
+
if (line) {
|
|
164
|
+
const request = JSON.parse(line);
|
|
165
|
+
handleRequest(apiKey, request);
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
buffer = buffer.slice(buffer.indexOf('\n') + 1);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// We have Content-Length, wait for body
|
|
175
|
+
if (buffer.length >= contentLength) {
|
|
176
|
+
const body = buffer.slice(0, contentLength);
|
|
177
|
+
buffer = buffer.slice(contentLength);
|
|
178
|
+
contentLength = -1;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const request = JSON.parse(body);
|
|
182
|
+
handleRequest(apiKey, request);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
sendError(null, -32700, 'Parse error');
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Handle graceful shutdown
|
|
193
|
+
process.on('SIGINT', () => process.exit(0));
|
|
194
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Handle a parsed request
|
|
199
|
+
*/
|
|
200
|
+
async function handleRequest(apiKey, request) {
|
|
201
|
+
try {
|
|
202
|
+
const response = await processRequest(apiKey, request);
|
|
203
|
+
if (response) {
|
|
204
|
+
sendResponse(response);
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
sendError(request.id, -32000, err.message);
|
|
208
|
+
}
|
|
209
|
+
}
|
package/lib/setup.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup and initialization for Ropilot CLI
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - First-time setup
|
|
6
|
+
* - Downloading system prompts
|
|
7
|
+
* - Creating project configs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { createInterface } from 'readline';
|
|
14
|
+
import {
|
|
15
|
+
loadGlobalConfig,
|
|
16
|
+
saveGlobalConfig,
|
|
17
|
+
setApiKey,
|
|
18
|
+
getApiKey,
|
|
19
|
+
EDGE_URL
|
|
20
|
+
} from './config.js';
|
|
21
|
+
|
|
22
|
+
// Prompts storage directory
|
|
23
|
+
const PROMPTS_DIR = join(homedir(), '.ropilot', 'prompts');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Prompt for user input
|
|
27
|
+
*/
|
|
28
|
+
function prompt(question) {
|
|
29
|
+
const rl = createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return new Promise(resolve => {
|
|
35
|
+
rl.question(question, answer => {
|
|
36
|
+
rl.close();
|
|
37
|
+
resolve(answer.trim());
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Download prompts from edge server
|
|
44
|
+
*/
|
|
45
|
+
async function downloadPrompts(apiKey) {
|
|
46
|
+
console.log('Downloading latest agent package...');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Fetch from edge server
|
|
50
|
+
const response = await fetch(`${EDGE_URL}/package`);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(`Server returned ${response.status}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pkg = await response.json();
|
|
56
|
+
console.log(`Package version: ${pkg.version}`);
|
|
57
|
+
|
|
58
|
+
// Ensure prompts directory exists
|
|
59
|
+
mkdirSync(PROMPTS_DIR, { recursive: true });
|
|
60
|
+
|
|
61
|
+
// Write package info
|
|
62
|
+
writeFileSync(join(PROMPTS_DIR, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
63
|
+
|
|
64
|
+
// Update config with version info
|
|
65
|
+
const config = loadGlobalConfig();
|
|
66
|
+
config.promptsVersion = pkg.version;
|
|
67
|
+
config.lastUpdated = new Date().toISOString();
|
|
68
|
+
saveGlobalConfig(config);
|
|
69
|
+
|
|
70
|
+
console.log('Agent package downloaded successfully!');
|
|
71
|
+
return pkg;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('Failed to download from server:', err.message);
|
|
74
|
+
console.log('Using bundled defaults...');
|
|
75
|
+
|
|
76
|
+
// Fallback to bundled defaults
|
|
77
|
+
const prompts = getDefaultPrompts();
|
|
78
|
+
mkdirSync(PROMPTS_DIR, { recursive: true });
|
|
79
|
+
for (const [name, content] of Object.entries(prompts)) {
|
|
80
|
+
writeFileSync(join(PROMPTS_DIR, name), content);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const config = loadGlobalConfig();
|
|
84
|
+
config.promptsVersion = '1.0.0-bundled';
|
|
85
|
+
config.lastUpdated = new Date().toISOString();
|
|
86
|
+
saveGlobalConfig(config);
|
|
87
|
+
|
|
88
|
+
return { version: '1.0.0-bundled', files: prompts };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get default prompts (bundled)
|
|
94
|
+
*/
|
|
95
|
+
function getDefaultPrompts() {
|
|
96
|
+
return {
|
|
97
|
+
'CLAUDE.md': `# Ropilot - AI-Powered Roblox Development
|
|
98
|
+
|
|
99
|
+
You are connected to Ropilot, an AI assistant for Roblox Studio development.
|
|
100
|
+
|
|
101
|
+
## Available Tools
|
|
102
|
+
|
|
103
|
+
Ropilot provides MCP tools for interacting with Roblox Studio:
|
|
104
|
+
|
|
105
|
+
### Reading & Inspection
|
|
106
|
+
- \`ropilot_listchildren\` - List children of an instance
|
|
107
|
+
- \`ropilot_getproperties\` - Get properties of an instance
|
|
108
|
+
- \`ropilot_search\` - Search for instances by name
|
|
109
|
+
- \`ropilot_searchbyclass\` - Search for instances by class name
|
|
110
|
+
|
|
111
|
+
### Playtesting
|
|
112
|
+
- \`ropilot_test_start_playtest\` - Start a playtest session
|
|
113
|
+
- \`ropilot_test_stop_playtest\` - Stop the current playtest
|
|
114
|
+
- \`ropilot_test_run_client_lua\` - Run Lua code on the client
|
|
115
|
+
- \`ropilot_test_run_server_lua\` - Run Lua code on the server
|
|
116
|
+
|
|
117
|
+
### Data Management
|
|
118
|
+
- \`ropilot_reset_data\` - Reset player data in DataStore
|
|
119
|
+
|
|
120
|
+
## Context Targeting
|
|
121
|
+
|
|
122
|
+
Commands can target different contexts:
|
|
123
|
+
- \`edit\` - Edit mode (default)
|
|
124
|
+
- \`server\` - Server context during playtest
|
|
125
|
+
- \`client\` - Client context during playtest
|
|
126
|
+
|
|
127
|
+
Use the \`target_context\` parameter to specify which context should execute the command.
|
|
128
|
+
|
|
129
|
+
## Best Practices
|
|
130
|
+
|
|
131
|
+
1. **Read before modify** - Always inspect the current state before making changes
|
|
132
|
+
2. **Use specific paths** - Reference instances by their full path (e.g., "Workspace.Map.Building")
|
|
133
|
+
3. **Delegate playtesting** - Use the roblox-tester subagent for playtest operations
|
|
134
|
+
4. **Test incrementally** - Start playtests to verify changes work correctly
|
|
135
|
+
`,
|
|
136
|
+
|
|
137
|
+
'subagents.json': JSON.stringify({
|
|
138
|
+
"roblox-tester": {
|
|
139
|
+
"description": "Fast subagent for executing Roblox playtest steps",
|
|
140
|
+
"tools": ["ropilot_test_*", "ropilot_get_*", "ropilot_execute_lua"],
|
|
141
|
+
"prompt": "You are a Roblox playtest executor. Run the specified test steps and report results concisely."
|
|
142
|
+
}
|
|
143
|
+
}, null, 2)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set up agent files in current project
|
|
149
|
+
*/
|
|
150
|
+
function setupProjectPrompts(pkg) {
|
|
151
|
+
const files = pkg.files || {};
|
|
152
|
+
|
|
153
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
154
|
+
const fullPath = filePath;
|
|
155
|
+
const dir = dirname(fullPath);
|
|
156
|
+
|
|
157
|
+
// Create directory if needed
|
|
158
|
+
if (dir && dir !== '.') {
|
|
159
|
+
mkdirSync(dir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if file exists
|
|
163
|
+
if (existsSync(fullPath)) {
|
|
164
|
+
console.log(` Exists: ${fullPath}`);
|
|
165
|
+
|
|
166
|
+
// For CLAUDE.local.md, append if Ropilot section not present
|
|
167
|
+
if (fullPath.includes('CLAUDE') && fullPath.endsWith('.md')) {
|
|
168
|
+
const existing = readFileSync(fullPath, 'utf-8');
|
|
169
|
+
if (!existing.includes('Ropilot') && !existing.includes('roblox-tester')) {
|
|
170
|
+
writeFileSync(fullPath, existing + '\n\n' + content);
|
|
171
|
+
console.log(` Updated: ${fullPath} (added Ropilot section)`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
writeFileSync(fullPath, content);
|
|
176
|
+
console.log(` Created: ${fullPath}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Initialize Ropilot in current project
|
|
183
|
+
*/
|
|
184
|
+
export async function init(providedApiKey = null) {
|
|
185
|
+
console.log('');
|
|
186
|
+
console.log('Welcome to Ropilot!');
|
|
187
|
+
console.log('===================');
|
|
188
|
+
console.log('');
|
|
189
|
+
|
|
190
|
+
// Get API key
|
|
191
|
+
let apiKey = providedApiKey || getApiKey();
|
|
192
|
+
|
|
193
|
+
if (!apiKey) {
|
|
194
|
+
console.log('To use Ropilot, you need an API key.');
|
|
195
|
+
console.log('Get one at: https://ropilot.ai');
|
|
196
|
+
console.log('');
|
|
197
|
+
apiKey = await prompt('Enter your API key (ropilot_...): ');
|
|
198
|
+
|
|
199
|
+
if (!apiKey || !apiKey.startsWith('ropilot_')) {
|
|
200
|
+
console.error('Invalid API key. Must start with "ropilot_"');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
setApiKey(apiKey);
|
|
205
|
+
console.log('API key saved!');
|
|
206
|
+
console.log('');
|
|
207
|
+
} else {
|
|
208
|
+
console.log(`Using API key: ${apiKey.slice(0, 20)}...`);
|
|
209
|
+
console.log('');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Download prompts
|
|
213
|
+
const pkg = await downloadPrompts(apiKey);
|
|
214
|
+
console.log('');
|
|
215
|
+
|
|
216
|
+
// Set up project prompts
|
|
217
|
+
console.log('Setting up project files...');
|
|
218
|
+
setupProjectPrompts(pkg);
|
|
219
|
+
console.log('');
|
|
220
|
+
|
|
221
|
+
// Done!
|
|
222
|
+
console.log('Setup complete!');
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('Next steps:');
|
|
225
|
+
console.log('1. Make sure Roblox Studio is running with the Ropilot plugin');
|
|
226
|
+
console.log('2. Add Ropilot to your AI IDE:');
|
|
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`);
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Update prompts to latest version
|
|
245
|
+
*/
|
|
246
|
+
export async function update() {
|
|
247
|
+
console.log('Checking for updates...');
|
|
248
|
+
|
|
249
|
+
const apiKey = getApiKey();
|
|
250
|
+
if (!apiKey) {
|
|
251
|
+
console.error('No API key configured. Run "ropilot init" first.');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await downloadPrompts(apiKey);
|
|
256
|
+
console.log('');
|
|
257
|
+
console.log('Update complete!');
|
|
258
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ropilot",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "AI-powered Roblox development assistant - MCP CLI",
|
|
5
|
+
"author": "whut",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ropilot": "./bin/ropilot.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./lib/index.js",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"roblox",
|
|
17
|
+
"ai",
|
|
18
|
+
"mcp",
|
|
19
|
+
"claude",
|
|
20
|
+
"model-context-protocol"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://ropilot.ai",
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"prompts/"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {}
|
|
29
|
+
}
|