Spaces:
Running
Running
some few fixes
Browse files- CLAUDE_INTEGRATION_GUIDE.md +0 -362
- DEPLOYMENT_FIX.md +0 -99
- HOW_CLAUDE_UPLOADS.md +0 -207
- HUGGINGFACE_DEBUG.md +0 -179
- HUGGINGFACE_DEPLOYMENT.md +0 -304
- IMPLEMENTATION_GUIDE.md +0 -211
- README-HF.md +0 -358
- REFACTORING_SUMMARY.md +0 -185
- VOICE_STUDIO_COMPLETE.md +0 -129
- VOICE_STUDIO_GUIDE.md +0 -233
- VOICE_STUDIO_IMPLEMENTATION.md +0 -276
- VOICE_STUDIO_QUICK_START.md +0 -124
- app/components/FlutterRunner.tsx +144 -173
- frontend-fetch-example.js +0 -462
- mcp-server.js +36 -43
- package-lock.json +17 -4
- package.json +1 -0
- pages/api/mcp.ts +0 -406
- test-api.js +0 -128
- test-download.js +0 -61
- test-mcp-changes.js +0 -210
- test-mcp-endpoint.js +0 -150
- test-mcp-local.js +0 -69
- test-voice-studio.js +0 -145
CLAUDE_INTEGRATION_GUIDE.md
DELETED
|
@@ -1,362 +0,0 @@
|
|
| 1 |
-
# Claude Desktop Integration Guide - Passkey System
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
Claude Desktop can now generate content and upload files directly to your ReubenOS on Hugging Face using the **passkey system** for secure storage!
|
| 5 |
-
|
| 6 |
-
## π How It Works
|
| 7 |
-
|
| 8 |
-
### **Two Storage Options:**
|
| 9 |
-
|
| 10 |
-
1. **π Secure Data (with Passkey)**
|
| 11 |
-
- Files are private and isolated by passkey
|
| 12 |
-
- Each passkey creates a separate secure folder
|
| 13 |
-
- Perfect for personal documents, code, quizzes
|
| 14 |
-
|
| 15 |
-
2. **π’ Public Files**
|
| 16 |
-
- Files are publicly accessible to everyone
|
| 17 |
-
- No passkey required
|
| 18 |
-
- Great for sharing content
|
| 19 |
-
|
| 20 |
-
## π Available MCP Tools
|
| 21 |
-
|
| 22 |
-
### 1. **save_file**
|
| 23 |
-
Save any file to ReubenOS (code, documents, data, etc.)
|
| 24 |
-
|
| 25 |
-
**Examples:**
|
| 26 |
-
|
| 27 |
-
```javascript
|
| 28 |
-
// Save a Flutter app (secure)
|
| 29 |
-
{
|
| 30 |
-
"fileName": "main.dart",
|
| 31 |
-
"content": "import 'package:flutter/material.dart'...",
|
| 32 |
-
"passkey": "my-secret-key"
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
// Save a public document
|
| 36 |
-
{
|
| 37 |
-
"fileName": "tutorial.md",
|
| 38 |
-
"content": "# Flutter Tutorial\n...",
|
| 39 |
-
"isPublic": true
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
// Save a LaTeX document
|
| 43 |
-
{
|
| 44 |
-
"fileName": "report.tex",
|
| 45 |
-
"content": "\\documentclass{article}...",
|
| 46 |
-
"passkey": "my-secret-key"
|
| 47 |
-
}
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
### 2. **list_files**
|
| 51 |
-
List all files in your storage
|
| 52 |
-
|
| 53 |
-
**Examples:**
|
| 54 |
-
|
| 55 |
-
```javascript
|
| 56 |
-
// List your secure files
|
| 57 |
-
{
|
| 58 |
-
"passkey": "my-secret-key"
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
// List public files
|
| 62 |
-
{
|
| 63 |
-
"isPublic": true
|
| 64 |
-
}
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
### 3. **delete_file**
|
| 68 |
-
Delete a specific file
|
| 69 |
-
|
| 70 |
-
**Examples:**
|
| 71 |
-
|
| 72 |
-
```javascript
|
| 73 |
-
// Delete from secure storage
|
| 74 |
-
{
|
| 75 |
-
"fileName": "old_file.dart",
|
| 76 |
-
"passkey": "my-secret-key"
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
// Delete from public folder
|
| 80 |
-
{
|
| 81 |
-
"fileName": "old_doc.md",
|
| 82 |
-
"isPublic": true
|
| 83 |
-
}
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
### 4. **deploy_quiz**
|
| 87 |
-
Deploy an interactive quiz to ReubenOS
|
| 88 |
-
|
| 89 |
-
**Example:**
|
| 90 |
-
|
| 91 |
-
```javascript
|
| 92 |
-
{
|
| 93 |
-
"passkey": "my-secret-key",
|
| 94 |
-
"quizData": {
|
| 95 |
-
"title": "Flutter Basics Quiz",
|
| 96 |
-
"description": "Test your Flutter knowledge",
|
| 97 |
-
"timeLimit": 15,
|
| 98 |
-
"questions": [
|
| 99 |
-
{
|
| 100 |
-
"id": "q1",
|
| 101 |
-
"question": "What is a StatelessWidget?",
|
| 102 |
-
"type": "multiple-choice",
|
| 103 |
-
"options": [
|
| 104 |
-
"A widget that never changes",
|
| 105 |
-
"A widget with state",
|
| 106 |
-
"A button widget",
|
| 107 |
-
"A layout widget"
|
| 108 |
-
],
|
| 109 |
-
"correctAnswer": 0,
|
| 110 |
-
"explanation": "StatelessWidget is immutable",
|
| 111 |
-
"points": 1
|
| 112 |
-
},
|
| 113 |
-
{
|
| 114 |
-
"id": "q2",
|
| 115 |
-
"question": "Flutter uses Dart language",
|
| 116 |
-
"type": "true-false",
|
| 117 |
-
"correctAnswer": true,
|
| 118 |
-
"points": 1
|
| 119 |
-
}
|
| 120 |
-
]
|
| 121 |
-
}
|
| 122 |
-
}
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
## π¬ How Claude Can Use This
|
| 126 |
-
|
| 127 |
-
### **Conversation Examples:**
|
| 128 |
-
|
| 129 |
-
**User:** "Create a Flutter counter app and save it to my ReubenOS"
|
| 130 |
-
|
| 131 |
-
**Claude Response:**
|
| 132 |
-
β
I'll create a Flutter counter app and save it using your passkey.
|
| 133 |
-
|
| 134 |
-
```dart
|
| 135 |
-
import 'package:flutter/material.dart';
|
| 136 |
-
|
| 137 |
-
void main() => runApp(CounterApp());
|
| 138 |
-
|
| 139 |
-
class CounterApp extends StatefulWidget {
|
| 140 |
-
@override
|
| 141 |
-
_CounterAppState createState() => _CounterAppState();
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
class _CounterAppState extends State<CounterApp> {
|
| 145 |
-
int _counter = 0;
|
| 146 |
-
|
| 147 |
-
void _incrementCounter() {
|
| 148 |
-
setState(() {
|
| 149 |
-
_counter++;
|
| 150 |
-
});
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
@override
|
| 154 |
-
Widget build(BuildContext context) {
|
| 155 |
-
return MaterialApp(...);
|
| 156 |
-
}
|
| 157 |
-
}
|
| 158 |
-
```
|
| 159 |
-
|
| 160 |
-
*[Uses MCP save_file tool]*
|
| 161 |
-
|
| 162 |
-
β
File saved successfully!
|
| 163 |
-
π File: counter_app.dart
|
| 164 |
-
π Location: π Secure Data (Key: your-passkey)
|
| 165 |
-
|
| 166 |
-
---
|
| 167 |
-
|
| 168 |
-
**User:** "Create a quiz about Python and deploy it"
|
| 169 |
-
|
| 170 |
-
**Claude Response:**
|
| 171 |
-
β
I'll create a Python quiz and deploy it to your ReubenOS.
|
| 172 |
-
|
| 173 |
-
*[Uses MCP deploy_quiz tool]*
|
| 174 |
-
|
| 175 |
-
β
Quiz deployed successfully!
|
| 176 |
-
π Quiz: Python Fundamentals
|
| 177 |
-
π Questions: 5
|
| 178 |
-
β±οΈ Time Limit: 10 min
|
| 179 |
-
π― Total Points: 5
|
| 180 |
-
π Location: π Secure (Key: your-passkey)
|
| 181 |
-
|
| 182 |
-
π Quiz is now available in Reuben OS Quiz App!
|
| 183 |
-
|
| 184 |
-
---
|
| 185 |
-
|
| 186 |
-
**User:** "Save this tutorial as a public file so others can see it"
|
| 187 |
-
|
| 188 |
-
**Claude Response:**
|
| 189 |
-
β
I'll save this tutorial to the public folder.
|
| 190 |
-
|
| 191 |
-
*[Uses MCP save_file with isPublic=true]*
|
| 192 |
-
|
| 193 |
-
β
File saved successfully!
|
| 194 |
-
π File: flutter_tutorial.md
|
| 195 |
-
π Location: π’ Public Folder
|
| 196 |
-
π URL: https://mcp-1st-birthday-reuben-os.hf.space/data/public/flutter_tutorial.md
|
| 197 |
-
|
| 198 |
-
## π Passkey Best Practices
|
| 199 |
-
|
| 200 |
-
1. **Choose a Memorable Passkey**
|
| 201 |
-
- Use alphanumeric characters, hyphens, or underscores
|
| 202 |
-
- Minimum 4 characters
|
| 203 |
-
- Example: `my-flutter-projects`, `john-doe-123`
|
| 204 |
-
|
| 205 |
-
2. **Organize by Project**
|
| 206 |
-
- Use different passkeys for different projects
|
| 207 |
-
- Example: `flutter-apps`, `latex-docs`, `quiz-bank`
|
| 208 |
-
|
| 209 |
-
3. **Public vs Private**
|
| 210 |
-
- Use **public** for tutorials, examples, shared content
|
| 211 |
-
- Use **passkey** for personal work, drafts, private quizzes
|
| 212 |
-
|
| 213 |
-
## π Accessing Files in ReubenOS
|
| 214 |
-
|
| 215 |
-
### **Via Web Interface:**
|
| 216 |
-
|
| 217 |
-
1. Open https://mcp-1st-birthday-reuben-os.hf.space
|
| 218 |
-
2. Click "File Manager" in the dock
|
| 219 |
-
3. For **Secure Data**:
|
| 220 |
-
- Click "Secure Data" in sidebar
|
| 221 |
-
- Enter your passkey
|
| 222 |
-
- Your files appear!
|
| 223 |
-
4. For **Public Files**:
|
| 224 |
-
- Click "Public Files" in sidebar
|
| 225 |
-
- All public files are visible
|
| 226 |
-
|
| 227 |
-
### **Via Claude Desktop:**
|
| 228 |
-
|
| 229 |
-
Use the `list_files` tool to see what's stored:
|
| 230 |
-
|
| 231 |
-
```javascript
|
| 232 |
-
// Your secure files
|
| 233 |
-
{ "passkey": "your-passkey" }
|
| 234 |
-
|
| 235 |
-
// Public files
|
| 236 |
-
{ "isPublic": true }
|
| 237 |
-
```
|
| 238 |
-
|
| 239 |
-
## π― Common Use Cases
|
| 240 |
-
|
| 241 |
-
### **1. Flutter Development**
|
| 242 |
-
```javascript
|
| 243 |
-
// Save main.dart
|
| 244 |
-
save_file({
|
| 245 |
-
fileName: "main.dart",
|
| 246 |
-
content: "...",
|
| 247 |
-
passkey: "flutter-project-1"
|
| 248 |
-
})
|
| 249 |
-
|
| 250 |
-
// Save widget file
|
| 251 |
-
save_file({
|
| 252 |
-
fileName: "custom_widget.dart",
|
| 253 |
-
content: "...",
|
| 254 |
-
passkey: "flutter-project-1"
|
| 255 |
-
})
|
| 256 |
-
```
|
| 257 |
-
|
| 258 |
-
### **2. LaTeX Documents**
|
| 259 |
-
```javascript
|
| 260 |
-
save_file({
|
| 261 |
-
fileName: "thesis.tex",
|
| 262 |
-
content: "\\documentclass{article}...",
|
| 263 |
-
passkey: "academic-work"
|
| 264 |
-
})
|
| 265 |
-
```
|
| 266 |
-
|
| 267 |
-
### **3. Quiz Creation**
|
| 268 |
-
```javascript
|
| 269 |
-
deploy_quiz({
|
| 270 |
-
passkey: "teaching-materials",
|
| 271 |
-
quizData: {
|
| 272 |
-
title: "Week 1 Quiz",
|
| 273 |
-
questions: [...]
|
| 274 |
-
}
|
| 275 |
-
})
|
| 276 |
-
```
|
| 277 |
-
|
| 278 |
-
### **4. Public Tutorials**
|
| 279 |
-
```javascript
|
| 280 |
-
save_file({
|
| 281 |
-
fileName: "react-guide.md",
|
| 282 |
-
content: "# React Guide...",
|
| 283 |
-
isPublic: true
|
| 284 |
-
})
|
| 285 |
-
```
|
| 286 |
-
|
| 287 |
-
## π Workflow Example
|
| 288 |
-
|
| 289 |
-
**Scenario:** Create a complete Flutter project
|
| 290 |
-
|
| 291 |
-
1. **Claude creates main.dart:**
|
| 292 |
-
```javascript
|
| 293 |
-
save_file({
|
| 294 |
-
fileName: "main.dart",
|
| 295 |
-
content: "...",
|
| 296 |
-
passkey: "my-flutter-app"
|
| 297 |
-
})
|
| 298 |
-
```
|
| 299 |
-
|
| 300 |
-
2. **Claude creates README:**
|
| 301 |
-
```javascript
|
| 302 |
-
save_file({
|
| 303 |
-
fileName: "README.md",
|
| 304 |
-
content: "# My Flutter App...",
|
| 305 |
-
passkey: "my-flutter-app"
|
| 306 |
-
})
|
| 307 |
-
```
|
| 308 |
-
|
| 309 |
-
3. **Claude creates a quiz for testing:**
|
| 310 |
-
```javascript
|
| 311 |
-
deploy_quiz({
|
| 312 |
-
passkey: "my-flutter-app",
|
| 313 |
-
quizData: {
|
| 314 |
-
title: "Flutter App Quiz",
|
| 315 |
-
questions: [...]
|
| 316 |
-
}
|
| 317 |
-
})
|
| 318 |
-
```
|
| 319 |
-
|
| 320 |
-
4. **User opens ReubenOS:**
|
| 321 |
-
- Opens File Manager β Secure Data
|
| 322 |
-
- Enters passkey: `my-flutter-app`
|
| 323 |
-
- Sees all 3 files!
|
| 324 |
-
- Opens Quiz App to take the quiz
|
| 325 |
-
|
| 326 |
-
## π Direct URLs
|
| 327 |
-
|
| 328 |
-
Files are accessible via:
|
| 329 |
-
- **Public:** `https://mcp-1st-birthday-reuben-os.hf.space/data/public/{filename}`
|
| 330 |
-
- **Secure:** Requires passkey authentication through the web interface
|
| 331 |
-
|
| 332 |
-
## βοΈ Configuration
|
| 333 |
-
|
| 334 |
-
The MCP server is configured in:
|
| 335 |
-
`~/Library/Application Support/Claude/claude_desktop_config.json`
|
| 336 |
-
|
| 337 |
-
```json
|
| 338 |
-
{
|
| 339 |
-
"mcpServers": {
|
| 340 |
-
"reubenos": {
|
| 341 |
-
"command": "node",
|
| 342 |
-
"args": ["/path/to/mcp-server.js"],
|
| 343 |
-
"env": {
|
| 344 |
-
"REUBENOS_URL": "https://mcp-1st-birthday-reuben-os.hf.space"
|
| 345 |
-
}
|
| 346 |
-
}
|
| 347 |
-
}
|
| 348 |
-
}
|
| 349 |
-
```
|
| 350 |
-
|
| 351 |
-
## π Summary
|
| 352 |
-
|
| 353 |
-
Claude can now:
|
| 354 |
-
- β
Generate Flutter/Dart code and save it
|
| 355 |
-
- β
Create LaTeX documents
|
| 356 |
-
- β
Deploy interactive quizzes
|
| 357 |
-
- β
Save any text-based files
|
| 358 |
-
- β
Use passkeys for private storage
|
| 359 |
-
- β
Share files publicly
|
| 360 |
-
- β
List and manage files
|
| 361 |
-
|
| 362 |
-
All files are **accessible immediately** in your ReubenOS web interface on Hugging Face! π
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEPLOYMENT_FIX.md
DELETED
|
@@ -1,99 +0,0 @@
|
|
| 1 |
-
# π§ Fix for Reuben OS Quiz & File Saving Issues
|
| 2 |
-
|
| 3 |
-
## The Problem
|
| 4 |
-
Your Hugging Face Space is using the OLD API structure. The quiz and file saving appear to work in Claude but don't actually save because the Hugging Face deployment doesn't have the new `/api/mcp-handler` route.
|
| 5 |
-
|
| 6 |
-
## Quick Solution
|
| 7 |
-
|
| 8 |
-
### Option 1: Deploy to Hugging Face (Recommended)
|
| 9 |
-
|
| 10 |
-
1. **Push these files to your Hugging Face Space:**
|
| 11 |
-
```bash
|
| 12 |
-
# In your Hugging Face Space repository
|
| 13 |
-
cp /Users/reubenfernandes/Desktop/Mcp-hackathon-winter25/pages/api/mcp-handler.js pages/api/
|
| 14 |
-
git add pages/api/mcp-handler.js
|
| 15 |
-
git commit -m "Add new MCP handler with session prefix strategy"
|
| 16 |
-
git push
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
2. **Restart Claude Desktop** with the new configuration
|
| 20 |
-
|
| 21 |
-
3. **Test with your session ID:**
|
| 22 |
-
- Session: `session_1763722877048_527d6bb8b7473568`
|
| 23 |
-
- Tell Claude: "Save a test file to my session"
|
| 24 |
-
|
| 25 |
-
### Option 2: Test Locally First
|
| 26 |
-
|
| 27 |
-
1. **Start your local Next.js server:**
|
| 28 |
-
```bash
|
| 29 |
-
cd /Users/reubenfernandes/Desktop/Mcp-hackathon-winter25
|
| 30 |
-
npm run dev
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
2. **Update Claude Desktop config to use local URL:**
|
| 34 |
-
```json
|
| 35 |
-
{
|
| 36 |
-
"mcpServers": {
|
| 37 |
-
"reubenos": {
|
| 38 |
-
"command": "node",
|
| 39 |
-
"args": ["/Users/reubenfernandes/Desktop/Mcp-hackathon-winter25/mcp-server.js"],
|
| 40 |
-
"env": {
|
| 41 |
-
"REUBENOS_URL": "http://localhost:3000"
|
| 42 |
-
}
|
| 43 |
-
}
|
| 44 |
-
}
|
| 45 |
-
}
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
3. **Test the functionality locally**
|
| 49 |
-
|
| 50 |
-
## What We Fixed
|
| 51 |
-
|
| 52 |
-
### β
Simplified Architecture
|
| 53 |
-
- Only 2 MCP tools: `manage_files` and `deploy_quiz`
|
| 54 |
-
- Stateless prefix strategy: `{sessionId}_{filename}`
|
| 55 |
-
- No complex session management
|
| 56 |
-
|
| 57 |
-
### β
New Features
|
| 58 |
-
- Save to public folder: `action: "save_public"`
|
| 59 |
-
- Automatic quiz detection
|
| 60 |
-
- Clean file names (prefixes hidden from users)
|
| 61 |
-
|
| 62 |
-
### β
File Structure
|
| 63 |
-
```
|
| 64 |
-
/tmp/
|
| 65 |
-
session_1763722877048_527d6bb8b7473568_quiz.json
|
| 66 |
-
session_1763722877048_527d6bb8b7473568_main.dart
|
| 67 |
-
session_1763722877048_527d6bb8b7473568_japanese_song.txt
|
| 68 |
-
|
| 69 |
-
/public/uploads/
|
| 70 |
-
public_files_here.txt (no session prefix)
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
## Testing Your Fix
|
| 74 |
-
|
| 75 |
-
Run this command to verify everything works:
|
| 76 |
-
```bash
|
| 77 |
-
node test-api.js
|
| 78 |
-
```
|
| 79 |
-
|
| 80 |
-
You should see:
|
| 81 |
-
- β
File save: SUCCESS
|
| 82 |
-
- β
Quiz deploy: SUCCESS
|
| 83 |
-
- β
File retrieval: SUCCESS
|
| 84 |
-
- β
Public save: SUCCESS
|
| 85 |
-
|
| 86 |
-
## Current Status
|
| 87 |
-
|
| 88 |
-
β
**MCP Server**: Updated and ready
|
| 89 |
-
β
**API Route**: Created but needs deployment
|
| 90 |
-
β **Hugging Face**: Still using old API
|
| 91 |
-
β³ **Quiz Display**: Will work after deployment
|
| 92 |
-
|
| 93 |
-
## Next Steps
|
| 94 |
-
|
| 95 |
-
1. Deploy the new API route to Hugging Face
|
| 96 |
-
2. Your quiz and file saving will work immediately
|
| 97 |
-
3. No changes needed to the frontend (it already polls correctly)
|
| 98 |
-
|
| 99 |
-
The Japanese song and quiz you tried to save will work as soon as you deploy the new API route!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HOW_CLAUDE_UPLOADS.md
DELETED
|
@@ -1,207 +0,0 @@
|
|
| 1 |
-
# How Claude Desktop Uploads Files to ReubenOS
|
| 2 |
-
|
| 3 |
-
## β
YES! Claude Can Upload Files to Your Hugging Face ReubenOS!
|
| 4 |
-
|
| 5 |
-
### **The Flow:**
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
Claude Desktop (Local)
|
| 9 |
-
β
|
| 10 |
-
MCP Server (mcp-server.js)
|
| 11 |
-
β
|
| 12 |
-
API Call to Hugging Face
|
| 13 |
-
β
|
| 14 |
-
/api/mcp-handler endpoint
|
| 15 |
-
β
|
| 16 |
-
Saves to /data directory
|
| 17 |
-
β
|
| 18 |
-
Files appear in ReubenOS web interface!
|
| 19 |
-
```
|
| 20 |
-
|
| 21 |
-
## π― What Claude Can Do
|
| 22 |
-
|
| 23 |
-
### **1. Generate & Save Code**
|
| 24 |
-
```
|
| 25 |
-
User: "Create a Flutter login screen and save it"
|
| 26 |
-
|
| 27 |
-
Claude:
|
| 28 |
-
- Generates Flutter/Dart code
|
| 29 |
-
- Uses MCP save_file tool
|
| 30 |
-
- Saves to your Hugging Face ReubenOS
|
| 31 |
-
- You can immediately open it in Flutter IDE!
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
### **2. Create & Deploy Quizzes**
|
| 35 |
-
```
|
| 36 |
-
User: "Create a quiz about React and deploy it"
|
| 37 |
-
|
| 38 |
-
Claude:
|
| 39 |
-
- Creates quiz with questions
|
| 40 |
-
- Uses MCP deploy_quiz tool
|
| 41 |
-
- Deploys to your ReubenOS
|
| 42 |
-
- You can take the quiz in Quiz App!
|
| 43 |
-
```
|
| 44 |
-
|
| 45 |
-
### **3. Generate Documents**
|
| 46 |
-
```
|
| 47 |
-
User: "Write a LaTeX report and save it"
|
| 48 |
-
|
| 49 |
-
Claude:
|
| 50 |
-
- Writes LaTeX content
|
| 51 |
-
- Saves to your ReubenOS
|
| 52 |
-
- You can edit it in LaTeX Studio!
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
## π Passkey System
|
| 56 |
-
|
| 57 |
-
### **How It Works:**
|
| 58 |
-
|
| 59 |
-
1. **You choose a passkey** (e.g., "my-project-123")
|
| 60 |
-
2. **Claude uses that passkey** when saving files
|
| 61 |
-
3. **Files are isolated** by passkey on Hugging Face
|
| 62 |
-
4. **You access files** by entering the same passkey in ReubenOS
|
| 63 |
-
|
| 64 |
-
### **Example Conversation:**
|
| 65 |
-
|
| 66 |
-
```
|
| 67 |
-
User: "My passkey is 'john-flutter-apps'. Create a todo app."
|
| 68 |
-
|
| 69 |
-
Claude:
|
| 70 |
-
- Creates todo_app.dart
|
| 71 |
-
- Calls save_file with passkey="john-flutter-apps"
|
| 72 |
-
- Saves to /data/john-flutter-apps/ on Hugging Face
|
| 73 |
-
|
| 74 |
-
You in ReubenOS:
|
| 75 |
-
- Open File Manager β Secure Data
|
| 76 |
-
- Enter passkey: john-flutter-apps
|
| 77 |
-
- See your todo_app.dart file!
|
| 78 |
-
- Open it in Flutter IDE
|
| 79 |
-
```
|
| 80 |
-
|
| 81 |
-
## π Public Files
|
| 82 |
-
|
| 83 |
-
Claude can also save to public folder (no passkey):
|
| 84 |
-
|
| 85 |
-
```
|
| 86 |
-
User: "Create a React tutorial and make it public"
|
| 87 |
-
|
| 88 |
-
Claude:
|
| 89 |
-
- Creates tutorial.md
|
| 90 |
-
- Saves with isPublic=true
|
| 91 |
-
- File is at: /data/public/tutorial.md
|
| 92 |
-
- Anyone can access it!
|
| 93 |
-
```
|
| 94 |
-
|
| 95 |
-
## π Available MCP Tools
|
| 96 |
-
|
| 97 |
-
| Tool | Purpose | Example |
|
| 98 |
-
|------|---------|---------|
|
| 99 |
-
| `save_file` | Save any file | Code, docs, data |
|
| 100 |
-
| `list_files` | List your files | See what's stored |
|
| 101 |
-
| `delete_file` | Remove files | Clean up old files |
|
| 102 |
-
| `deploy_quiz` | Create quizzes | Interactive assessments |
|
| 103 |
-
|
| 104 |
-
## π Setup (Already Done!)
|
| 105 |
-
|
| 106 |
-
β
MCP server configured: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
| 107 |
-
β
Points to: `https://mcp-1st-birthday-reuben-os.hf.space`
|
| 108 |
-
β
Using passkey authentication
|
| 109 |
-
β
Persistent storage on Hugging Face `/data` directory
|
| 110 |
-
|
| 111 |
-
## π‘ Usage Examples
|
| 112 |
-
|
| 113 |
-
### **Example 1: Flutter Project**
|
| 114 |
-
```
|
| 115 |
-
User: "Create a weather app with API integration. Passkey: weather-project"
|
| 116 |
-
|
| 117 |
-
Claude will:
|
| 118 |
-
1. Generate main.dart
|
| 119 |
-
2. Generate API service file
|
| 120 |
-
3. Generate models
|
| 121 |
-
4. Save all with passkey="weather-project"
|
| 122 |
-
5. You access them in ReubenOS File Manager!
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
### **Example 2: Study Materials**
|
| 126 |
-
```
|
| 127 |
-
User: "Create a Python quiz for beginners. Passkey: teaching-2024"
|
| 128 |
-
|
| 129 |
-
Claude will:
|
| 130 |
-
1. Create quiz with 10 questions
|
| 131 |
-
2. Deploy with passkey="teaching-2024"
|
| 132 |
-
3. Quiz appears in ReubenOS Quiz App!
|
| 133 |
-
4. Students can take it!
|
| 134 |
-
```
|
| 135 |
-
|
| 136 |
-
### **Example 3: Documentation**
|
| 137 |
-
```
|
| 138 |
-
User: "Write API documentation. Make it public."
|
| 139 |
-
|
| 140 |
-
Claude will:
|
| 141 |
-
1. Create api-docs.md
|
| 142 |
-
2. Save to public folder
|
| 143 |
-
3. File is publicly accessible!
|
| 144 |
-
4. Anyone can view it!
|
| 145 |
-
```
|
| 146 |
-
|
| 147 |
-
## π Complete Workflow
|
| 148 |
-
|
| 149 |
-
1. **User talks to Claude Desktop**
|
| 150 |
-
- "Create X and save it with passkey Y"
|
| 151 |
-
|
| 152 |
-
2. **Claude generates content**
|
| 153 |
-
- Uses MCP tools to save to ReubenOS
|
| 154 |
-
|
| 155 |
-
3. **Files saved to Hugging Face**
|
| 156 |
-
- Stored in `/data/{passkey}/` directory
|
| 157 |
-
|
| 158 |
-
4. **User opens ReubenOS web interface**
|
| 159 |
-
- Opens File Manager
|
| 160 |
-
- Clicks "Secure Data"
|
| 161 |
-
- Enters passkey
|
| 162 |
-
- Files appear!
|
| 163 |
-
|
| 164 |
-
5. **User works with files**
|
| 165 |
-
- Open in Flutter IDE, LaTeX Studio, etc.
|
| 166 |
-
- Edit, run, compile
|
| 167 |
-
- Take quizzes
|
| 168 |
-
|
| 169 |
-
## π Benefits
|
| 170 |
-
|
| 171 |
-
β
**No manual file uploads** - Claude does it for you
|
| 172 |
-
β
**Instant access** - Files appear immediately
|
| 173 |
-
β
**Organized** - Passkeys keep projects separate
|
| 174 |
-
β
**Secure** - Only you have the passkey
|
| 175 |
-
β
**Public sharing** - Optional public folder
|
| 176 |
-
β
**Persistent** - Files stored on Hugging Face's `/data`
|
| 177 |
-
|
| 178 |
-
## π± Access Anywhere
|
| 179 |
-
|
| 180 |
-
- **Desktop**: https://mcp-1st-birthday-reuben-os.hf.space
|
| 181 |
-
- **Mobile**: Same URL works on mobile!
|
| 182 |
-
- **Files persist** across deployments
|
| 183 |
-
- **No data loss** with Hugging Face Spaces
|
| 184 |
-
|
| 185 |
-
## π New vs Old System
|
| 186 |
-
|
| 187 |
-
| Feature | Old (Session) | New (Passkey) |
|
| 188 |
-
|---------|--------------|---------------|
|
| 189 |
-
| Authentication | Session ID | Passkey |
|
| 190 |
-
| Storage | Temporary /tmp | Persistent /data |
|
| 191 |
-
| Organization | By session | By passkey |
|
| 192 |
-
| Sharing | Complicated | Simple (public folder) |
|
| 193 |
-
| Claude Integration | β
Yes | β
Yes (Improved!) |
|
| 194 |
-
| File Persistence | β Lost on rebuild | β
Persists forever |
|
| 195 |
-
|
| 196 |
-
## π― Summary
|
| 197 |
-
|
| 198 |
-
**Yes, Claude Desktop can upload files to your Hugging Face ReubenOS!**
|
| 199 |
-
|
| 200 |
-
The flow is:
|
| 201 |
-
1. You give Claude a passkey
|
| 202 |
-
2. Claude generates content
|
| 203 |
-
3. Claude uses MCP tools to save
|
| 204 |
-
4. Files go to Hugging Face
|
| 205 |
-
5. You access them in ReubenOS web interface
|
| 206 |
-
|
| 207 |
-
It's **automatic, secure, and persistent**! π
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HUGGINGFACE_DEBUG.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
| 1 |
-
# Debugging Hugging Face Space Deployment
|
| 2 |
-
|
| 3 |
-
## Current Status
|
| 4 |
-
β
Code successfully pushed to Hugging Face (commit: 265f878)
|
| 5 |
-
β³ Space shows "Running" status - waiting for build/deployment
|
| 6 |
-
|
| 7 |
-
## Steps Completed
|
| 8 |
-
1. β
Merged `clean-master` into `master` branch
|
| 9 |
-
2. β
Pushed to GitHub (`origin/master`)
|
| 10 |
-
3. β
Pushed to Hugging Face (`huggingface/master` and `huggingface/main`)
|
| 11 |
-
|
| 12 |
-
## Debugging Steps
|
| 13 |
-
|
| 14 |
-
### 1. Check Build Logs on Hugging Face
|
| 15 |
-
1. Go to https://huggingface.co/spaces/MCP-1st-Birthday/Reuben_OS
|
| 16 |
-
2. Click on the **"Files"** tab
|
| 17 |
-
3. Look for **"Logs"** or **"Build"** section
|
| 18 |
-
4. Check for any errors during build
|
| 19 |
-
|
| 20 |
-
### 2. Common Issues & Solutions
|
| 21 |
-
|
| 22 |
-
#### **Issue: Space stuck on "Running"**
|
| 23 |
-
**Solution:**
|
| 24 |
-
- Wait 2-3 minutes for initial build
|
| 25 |
-
- Check if `Dockerfile` exists and is properly configured
|
| 26 |
-
- Verify `next.config.mjs` has correct settings
|
| 27 |
-
- Check if `package.json` dependencies can install
|
| 28 |
-
|
| 29 |
-
#### **Issue: Build fails with module errors**
|
| 30 |
-
**Solution:**
|
| 31 |
-
- Check that all imports are correct
|
| 32 |
-
- Verify no references to deleted files (SessionManager, etc.)
|
| 33 |
-
- Run `npm install` and `npm run build` locally first
|
| 34 |
-
|
| 35 |
-
#### **Issue: App loads but files don't appear**
|
| 36 |
-
**Solution:**
|
| 37 |
-
- The new passkey system requires user to:
|
| 38 |
-
1. Open File Manager
|
| 39 |
-
2. Click "Secure Data" in sidebar
|
| 40 |
-
3. Enter a passkey
|
| 41 |
-
4. Upload files
|
| 42 |
-
- For public files:
|
| 43 |
-
1. Click "Public Files" in sidebar
|
| 44 |
-
2. Upload files directly
|
| 45 |
-
|
| 46 |
-
### 3. Environment-Specific Issues
|
| 47 |
-
|
| 48 |
-
#### **Data Directory Permissions**
|
| 49 |
-
The app creates directories in `public/data/`:
|
| 50 |
-
```
|
| 51 |
-
public/
|
| 52 |
-
βββ data/
|
| 53 |
-
βββ public/ # Public files
|
| 54 |
-
βββ {passkey}/ # Secure files per passkey
|
| 55 |
-
```
|
| 56 |
-
|
| 57 |
-
**Check on Hugging Face:**
|
| 58 |
-
- Hugging Face Spaces may have read-only file system
|
| 59 |
-
- Persistent storage requires special configuration
|
| 60 |
-
- May need to use `/data` directory instead
|
| 61 |
-
|
| 62 |
-
#### **Fix for Hugging Face Persistent Storage:**
|
| 63 |
-
Update these routes to use Hugging Face's `/data` directory:
|
| 64 |
-
|
| 65 |
-
**In `/app/api/data/route.ts`:**
|
| 66 |
-
```typescript
|
| 67 |
-
const DATA_DIR = process.env.SPACE_ID
|
| 68 |
-
? '/data'
|
| 69 |
-
: path.join(process.cwd(), 'public', 'data')
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
**In `/app/api/files/route.ts`:**
|
| 73 |
-
```typescript
|
| 74 |
-
const DATA_DIR = process.env.SPACE_ID
|
| 75 |
-
? '/data'
|
| 76 |
-
: path.join(process.cwd(), 'public', 'data')
|
| 77 |
-
```
|
| 78 |
-
|
| 79 |
-
**In `/app/api/public/route.ts`:**
|
| 80 |
-
```typescript
|
| 81 |
-
const DATA_DIR = process.env.SPACE_ID
|
| 82 |
-
? '/data'
|
| 83 |
-
: path.join(process.cwd(), 'public', 'data')
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
### 4. Force Rebuild on Hugging Face
|
| 87 |
-
|
| 88 |
-
If the space doesn't auto-rebuild:
|
| 89 |
-
1. Go to space settings
|
| 90 |
-
2. Click **"Factory Reboot"** to force a rebuild
|
| 91 |
-
3. OR make a small commit:
|
| 92 |
-
```bash
|
| 93 |
-
git commit --allow-empty -m "Trigger rebuild"
|
| 94 |
-
git push huggingface master:main
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
### 5. Check Space Configuration
|
| 98 |
-
|
| 99 |
-
Verify your space has correct settings:
|
| 100 |
-
- **SDK**: Docker or nodejs
|
| 101 |
-
- **SDK version**: 18 (for Node.js)
|
| 102 |
-
- **App port**: Should match your app (3000 for Next.js)
|
| 103 |
-
|
| 104 |
-
### 6. Local Testing vs Production
|
| 105 |
-
|
| 106 |
-
**Key Differences:**
|
| 107 |
-
- **Local**: Uses `public/data` directory
|
| 108 |
-
- **Hugging Face**: Should use `/data` directory for persistence
|
| 109 |
-
- **Local**: Hot reload works
|
| 110 |
-
- **Hugging Face**: Requires rebuild on each push
|
| 111 |
-
|
| 112 |
-
### 7. Verify the Deployment
|
| 113 |
-
|
| 114 |
-
Once the space loads:
|
| 115 |
-
1. **Test Public Files:**
|
| 116 |
-
- Open File Manager
|
| 117 |
-
- Click "Public Files"
|
| 118 |
-
- Try uploading a file
|
| 119 |
-
- Verify it appears in the list
|
| 120 |
-
|
| 121 |
-
2. **Test Secure Storage:**
|
| 122 |
-
- Click "Secure Data"
|
| 123 |
-
- Enter any passkey (e.g., "test123")
|
| 124 |
-
- Upload a file
|
| 125 |
-
- Lock and unlock with same passkey
|
| 126 |
-
- Files should persist
|
| 127 |
-
|
| 128 |
-
### 8. Common Error Messages
|
| 129 |
-
|
| 130 |
-
**"Module not found"**
|
| 131 |
-
- Missing dependency in package.json
|
| 132 |
-
- Run: `npm install <missing-package>`
|
| 133 |
-
- Commit and push
|
| 134 |
-
|
| 135 |
-
**"Build failed"**
|
| 136 |
-
- Check Hugging Face build logs
|
| 137 |
-
- Look for TypeScript errors
|
| 138 |
-
- Verify all imports
|
| 139 |
-
|
| 140 |
-
**"Cannot write to directory"**
|
| 141 |
-
- File system is read-only
|
| 142 |
-
- Need to use `/data` directory
|
| 143 |
-
- Apply the fixes in Section 3
|
| 144 |
-
|
| 145 |
-
### 9. Monitoring Build Progress
|
| 146 |
-
|
| 147 |
-
Watch the space status:
|
| 148 |
-
- **Building**: Code is being compiled
|
| 149 |
-
- **Running**: App is starting
|
| 150 |
-
- **Ready**: App is live and accessible
|
| 151 |
-
- **Error**: Build failed - check logs
|
| 152 |
-
|
| 153 |
-
### 10. Quick Checklist
|
| 154 |
-
|
| 155 |
-
- [ ] Code pushed to Hugging Face
|
| 156 |
-
- [ ] Space shows "Building" or "Running"
|
| 157 |
-
- [ ] Wait 2-3 minutes for initial build
|
| 158 |
-
- [ ] No errors in build logs
|
| 159 |
-
- [ ] Space shows "Ready" status
|
| 160 |
-
- [ ] Can access the URL
|
| 161 |
-
- [ ] File Manager loads
|
| 162 |
-
- [ ] Can upload to Public Files
|
| 163 |
-
- [ ] Can create passkey and upload to Secure Data
|
| 164 |
-
|
| 165 |
-
## Next Steps
|
| 166 |
-
|
| 167 |
-
1. **Wait** for the current build to complete (2-3 minutes)
|
| 168 |
-
2. **Check** the Hugging Face space URL
|
| 169 |
-
3. **Review** build logs if there are errors
|
| 170 |
-
4. **Apply** the `/data` directory fix if file uploads fail
|
| 171 |
-
5. **Test** the file upload functionality
|
| 172 |
-
|
| 173 |
-
## Getting Help
|
| 174 |
-
|
| 175 |
-
If issues persist:
|
| 176 |
-
1. Share the Hugging Face build logs
|
| 177 |
-
2. Share any browser console errors
|
| 178 |
-
3. Check if the space is "Ready" or still "Building"
|
| 179 |
-
4. Verify the space settings are correct
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HUGGINGFACE_DEPLOYMENT.md
DELETED
|
@@ -1,304 +0,0 @@
|
|
| 1 |
-
# Hugging Face Spaces Deployment Guide
|
| 2 |
-
|
| 3 |
-
This guide will walk you through deploying ReubenOS to Hugging Face Spaces and configuring the MCP server for remote access.
|
| 4 |
-
|
| 5 |
-
## π Prerequisites
|
| 6 |
-
|
| 7 |
-
- Hugging Face account ([create one here](https://huggingface.co/join))
|
| 8 |
-
- Git installed locally
|
| 9 |
-
- Node.js 20+ installed (for running MCP server locally)
|
| 10 |
-
- Claude Desktop (optional, for MCP integration)
|
| 11 |
-
|
| 12 |
-
## π Deployment Steps
|
| 13 |
-
|
| 14 |
-
### Step 1: Create a Hugging Face Space
|
| 15 |
-
|
| 16 |
-
1. Go to https://huggingface.co/new-space
|
| 17 |
-
2. Fill in the details:
|
| 18 |
-
- **Owner**: Your username or organization
|
| 19 |
-
- **Space name**: `reubenos` (or your preferred name)
|
| 20 |
-
- **License**: MIT
|
| 21 |
-
- **SDK**: Select **Docker**
|
| 22 |
-
- **Visibility**: Public or Private (your choice)
|
| 23 |
-
3. Click **Create Space**
|
| 24 |
-
|
| 25 |
-
### Step 2: Clone and Prepare Repository
|
| 26 |
-
|
| 27 |
-
```bash
|
| 28 |
-
# Clone your ReubenOS repository
|
| 29 |
-
git clone <your-repo-url>
|
| 30 |
-
cd reubenos
|
| 31 |
-
|
| 32 |
-
# Add Hugging Face Space as a remote
|
| 33 |
-
git remote add hf https://huggingface.co/spaces/YOUR-USERNAME/reubenos
|
| 34 |
-
|
| 35 |
-
# If you haven't logged in to Hugging Face CLI yet:
|
| 36 |
-
# Install huggingface_hub
|
| 37 |
-
pip install huggingface_hub
|
| 38 |
-
|
| 39 |
-
# Login to Hugging Face
|
| 40 |
-
huggingface-cli login
|
| 41 |
-
```
|
| 42 |
-
|
| 43 |
-
### Step 3: Configure Environment Variables (Optional)
|
| 44 |
-
|
| 45 |
-
Create a `.env` file in your Space settings if needed:
|
| 46 |
-
|
| 47 |
-
1. Go to your Space settings: `https://huggingface.co/spaces/YOUR-USERNAME/reubenos/settings`
|
| 48 |
-
2. Navigate to **Variables and secrets**
|
| 49 |
-
3. Add the following secrets (if applicable):
|
| 50 |
-
- `GEMINI_API_KEY` - Your Google Gemini API key (for AI chat features)
|
| 51 |
-
- `NODE_ENV` - Set to `production`
|
| 52 |
-
|
| 53 |
-
### Step 4: Push to Hugging Face
|
| 54 |
-
|
| 55 |
-
```bash
|
| 56 |
-
# Push your code to Hugging Face Spaces
|
| 57 |
-
git push hf main
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### Step 5: Monitor Build Progress
|
| 61 |
-
|
| 62 |
-
1. Go to your Space page: `https://huggingface.co/spaces/YOUR-USERNAME/reubenos`
|
| 63 |
-
2. Click on the **Logs** tab to monitor the build process
|
| 64 |
-
3. Build typically takes 5-10 minutes
|
| 65 |
-
4. Once complete, you'll see "App running at..."
|
| 66 |
-
|
| 67 |
-
### Step 6: Access Your Deployed Application
|
| 68 |
-
|
| 69 |
-
Your ReubenOS instance will be available at:
|
| 70 |
-
```
|
| 71 |
-
https://YOUR-USERNAME-reubenos.hf.space
|
| 72 |
-
```
|
| 73 |
-
|
| 74 |
-
## π§ MCP Server Configuration
|
| 75 |
-
|
| 76 |
-
### Important: MCP Server Architecture
|
| 77 |
-
|
| 78 |
-
The MCP server (`mcp-server.js`) uses **stdio transport**, which means it runs locally on your machine and communicates with Claude Desktop via standard input/output. It **cannot** run directly on Hugging Face Spaces.
|
| 79 |
-
|
| 80 |
-
**However**, the MCP server can communicate with your deployed Hugging Face Space via HTTP!
|
| 81 |
-
|
| 82 |
-
### How It Works
|
| 83 |
-
|
| 84 |
-
```
|
| 85 |
-
βββββββββββββββββββ stdio ββββββββββββββββ
|
| 86 |
-
β Claude Desktop β ββββββββββββββββββββββββΊ β MCP Server β
|
| 87 |
-
β (AI Client) β β (Local) β
|
| 88 |
-
βββββββββββββββββββ ββββββββ¬ββββββββ
|
| 89 |
-
β
|
| 90 |
-
β HTTP/S
|
| 91 |
-
β
|
| 92 |
-
ββββββββΌβββββββββββββββββββ
|
| 93 |
-
β Hugging Face Space β
|
| 94 |
-
β (ReubenOS Backend) β
|
| 95 |
-
β β
|
| 96 |
-
β - File Storage β
|
| 97 |
-
β - Document Processing β
|
| 98 |
-
β - Web Interface β
|
| 99 |
-
βββββββββββββββββββββββββββ
|
| 100 |
-
```
|
| 101 |
-
|
| 102 |
-
### Configuring Claude Desktop
|
| 103 |
-
|
| 104 |
-
1. **Download the MCP server file** from your repository to your local machine
|
| 105 |
-
2. **Configure Claude Desktop** to use your deployed Space:
|
| 106 |
-
|
| 107 |
-
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
| 108 |
-
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
| 109 |
-
**Linux**: `~/.config/Claude/claude_desktop_config.json`
|
| 110 |
-
|
| 111 |
-
```json
|
| 112 |
-
{
|
| 113 |
-
"mcpServers": {
|
| 114 |
-
"reubenos": {
|
| 115 |
-
"command": "node",
|
| 116 |
-
"args": ["C:/path/to/your/local/mcp-server.js"],
|
| 117 |
-
"env": {
|
| 118 |
-
"REUBENOS_URL": "https://YOUR-USERNAME-reubenos.hf.space"
|
| 119 |
-
}
|
| 120 |
-
}
|
| 121 |
-
}
|
| 122 |
-
}
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
**Important**: Replace:
|
| 126 |
-
- `C:/path/to/your/local/mcp-server.js` with the actual path to your local `mcp-server.js` file
|
| 127 |
-
- `YOUR-USERNAME` with your Hugging Face username
|
| 128 |
-
|
| 129 |
-
3. **Restart Claude Desktop**
|
| 130 |
-
|
| 131 |
-
### Using the MCP Server
|
| 132 |
-
|
| 133 |
-
Once configured, you can use Claude to:
|
| 134 |
-
|
| 135 |
-
1. **Create Sessions**:
|
| 136 |
-
```
|
| 137 |
-
"Create a new session for my project"
|
| 138 |
-
```
|
| 139 |
-
|
| 140 |
-
2. **Upload Files**:
|
| 141 |
-
```
|
| 142 |
-
"Upload this document to my session"
|
| 143 |
-
```
|
| 144 |
-
|
| 145 |
-
3. **Generate Documents**:
|
| 146 |
-
```
|
| 147 |
-
"Generate a PDF document with the title 'Project Report' and content..."
|
| 148 |
-
```
|
| 149 |
-
|
| 150 |
-
4. **List Files**:
|
| 151 |
-
```
|
| 152 |
-
"Show me all files in my session"
|
| 153 |
-
```
|
| 154 |
-
|
| 155 |
-
5. **Download Files**:
|
| 156 |
-
```
|
| 157 |
-
"Download the file named 'report.pdf'"
|
| 158 |
-
```
|
| 159 |
-
|
| 160 |
-
## π Accessing the Web Interface
|
| 161 |
-
|
| 162 |
-
Your deployed Space provides a full desktop environment accessible at:
|
| 163 |
-
```
|
| 164 |
-
https://YOUR-USERNAME-reubenos.hf.space
|
| 165 |
-
```
|
| 166 |
-
|
| 167 |
-
Features available in the web interface:
|
| 168 |
-
- β
Desktop environment with draggable windows
|
| 169 |
-
- β
File manager for uploading/downloading files
|
| 170 |
-
- β
Document viewer for PDFs and other formats
|
| 171 |
-
- β
AI chat with Gemini integration
|
| 172 |
-
- β
Session management
|
| 173 |
-
- β
Web browser
|
| 174 |
-
- β
Terminal
|
| 175 |
-
- β
Calendar and productivity tools
|
| 176 |
-
|
| 177 |
-
## π Security Considerations
|
| 178 |
-
|
| 179 |
-
### Session Management
|
| 180 |
-
- Each session has a **unique session key**
|
| 181 |
-
- Store your session keys securely
|
| 182 |
-
- Don't share session keys publicly
|
| 183 |
-
- Use public folders only for non-sensitive data
|
| 184 |
-
|
| 185 |
-
### API Keys
|
| 186 |
-
- Never commit API keys to your repository
|
| 187 |
-
- Use Hugging Face Secrets for sensitive environment variables
|
| 188 |
-
- The `.gitignore` is configured to exclude `.env` files
|
| 189 |
-
|
| 190 |
-
### CORS and Network
|
| 191 |
-
- The deployed application accepts requests from any origin
|
| 192 |
-
- MCP server validates session keys for all protected operations
|
| 193 |
-
- Public files are accessible without authentication
|
| 194 |
-
|
| 195 |
-
## π Troubleshooting
|
| 196 |
-
|
| 197 |
-
### Build Fails on Hugging Face
|
| 198 |
-
|
| 199 |
-
**Check Logs**: Review the build logs in your Space's Logs tab
|
| 200 |
-
|
| 201 |
-
**Common Issues**:
|
| 202 |
-
- Missing dependencies: Ensure all packages are in `package.json`
|
| 203 |
-
- Memory limits: Hugging Face has resource limits for free spaces
|
| 204 |
-
- Build timeout: Complex builds may timeout; try optimizing Dockerfile
|
| 205 |
-
|
| 206 |
-
### MCP Server Cannot Connect
|
| 207 |
-
|
| 208 |
-
**Issue**: Claude Desktop shows "MCP server connection failed"
|
| 209 |
-
|
| 210 |
-
**Solutions**:
|
| 211 |
-
1. Verify `mcp-server.js` path in config is correct
|
| 212 |
-
2. Ensure Node.js is installed locally
|
| 213 |
-
3. Check that REUBENOS_URL points to your deployed Space
|
| 214 |
-
4. Restart Claude Desktop after config changes
|
| 215 |
-
|
| 216 |
-
**Verify with**:
|
| 217 |
-
```bash
|
| 218 |
-
# Test MCP server locally
|
| 219 |
-
REUBENOS_URL=https://YOUR-USERNAME-reubenos.hf.space node mcp-server.js
|
| 220 |
-
```
|
| 221 |
-
|
| 222 |
-
### Cannot Access Deployed Space
|
| 223 |
-
|
| 224 |
-
**Issue**: 404 or connection errors
|
| 225 |
-
|
| 226 |
-
**Solutions**:
|
| 227 |
-
1. Verify build completed successfully
|
| 228 |
-
2. Check Space visibility settings (public vs. private)
|
| 229 |
-
3. Ensure you're using the correct URL format
|
| 230 |
-
|
| 231 |
-
### Files Not Persisting
|
| 232 |
-
|
| 233 |
-
**Issue**: Uploaded files disappear after restart
|
| 234 |
-
|
| 235 |
-
**Note**: Hugging Face Spaces use **ephemeral storage**. Files uploaded to the Space will be lost on restart. For persistence:
|
| 236 |
-
|
| 237 |
-
**Solutions**:
|
| 238 |
-
1. Use Hugging Face Datasets for permanent storage
|
| 239 |
-
2. Integrate external storage (S3, etc.)
|
| 240 |
-
3. Download important files locally via MCP or web interface
|
| 241 |
-
|
| 242 |
-
## π Monitoring Your Space
|
| 243 |
-
|
| 244 |
-
### Viewing Logs
|
| 245 |
-
```
|
| 246 |
-
https://huggingface.co/spaces/YOUR-USERNAME/reubenos/logs
|
| 247 |
-
```
|
| 248 |
-
|
| 249 |
-
### Checking Usage
|
| 250 |
-
```
|
| 251 |
-
https://huggingface.co/spaces/YOUR-USERNAME/reubenos/settings
|
| 252 |
-
```
|
| 253 |
-
|
| 254 |
-
### Restarting Your Space
|
| 255 |
-
- Go to Settings β Factory reboot
|
| 256 |
-
- Or push a new commit to trigger rebuild
|
| 257 |
-
|
| 258 |
-
## π Updating Your Deployment
|
| 259 |
-
|
| 260 |
-
```bash
|
| 261 |
-
# Make changes to your code
|
| 262 |
-
git add .
|
| 263 |
-
git commit -m "Update feature XYZ"
|
| 264 |
-
|
| 265 |
-
# Push to Hugging Face
|
| 266 |
-
git push hf main
|
| 267 |
-
|
| 268 |
-
# Hugging Face will automatically rebuild
|
| 269 |
-
```
|
| 270 |
-
|
| 271 |
-
## π‘ Advanced Configuration
|
| 272 |
-
|
| 273 |
-
### Custom Domain
|
| 274 |
-
- Hugging Face Spaces supports custom domains
|
| 275 |
-
- Configure in Space settings under "Custom domain"
|
| 276 |
-
|
| 277 |
-
### Scaling
|
| 278 |
-
- Free tier: Limited resources
|
| 279 |
-
- Pro tier: More CPU, RAM, and persistent storage
|
| 280 |
-
- Enterprise: Custom resource allocation
|
| 281 |
-
|
| 282 |
-
### Monitoring
|
| 283 |
-
- Add custom health checks in `app/api/health/route.ts`
|
| 284 |
-
- Use the built-in logs for debugging
|
| 285 |
-
- Implement application-level logging as needed
|
| 286 |
-
|
| 287 |
-
## π Additional Resources
|
| 288 |
-
|
| 289 |
-
- [Hugging Face Spaces Documentation](https://huggingface.co/docs/hub/spaces)
|
| 290 |
-
- [Docker on Spaces](https://huggingface.co/docs/hub/spaces-sdks-docker)
|
| 291 |
-
- [Model Context Protocol Docs](https://modelcontextprotocol.io/)
|
| 292 |
-
- [Next.js Deployment Docs](https://nextjs.org/docs/deployment)
|
| 293 |
-
|
| 294 |
-
## π Getting Help
|
| 295 |
-
|
| 296 |
-
- **GitHub Issues**: Report bugs or request features
|
| 297 |
-
- **Hugging Face Forums**: Ask deployment questions
|
| 298 |
-
- **Discord/Community**: Join our community for support
|
| 299 |
-
|
| 300 |
-
---
|
| 301 |
-
|
| 302 |
-
**Happy Deploying! π**
|
| 303 |
-
|
| 304 |
-
If you successfully deploy ReubenOS to Hugging Face Spaces, consider sharing it with the community!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IMPLEMENTATION_GUIDE.md
DELETED
|
@@ -1,211 +0,0 @@
|
|
| 1 |
-
# Reuben OS - Stateless Prefix Strategy Implementation Guide
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
This implementation uses a simple "Stateless Prefix Strategy" to handle session isolation and file management without complex session state management. All files are stored in `/tmp` with a `{SessionID}_{Filename}` pattern.
|
| 5 |
-
|
| 6 |
-
## Architecture Components
|
| 7 |
-
|
| 8 |
-
### 1. API Route (`pages/api/mcp-handler.js`)
|
| 9 |
-
- **Location**: `/pages/api/mcp-handler.js`
|
| 10 |
-
- **Purpose**: Handles all file operations with session prefix logic
|
| 11 |
-
- **Key Features**:
|
| 12 |
-
- Validates session IDs (alphanumeric + hyphens/underscores only)
|
| 13 |
-
- Prefixes all files with `{sessionId}_` when saving to `/tmp`
|
| 14 |
-
- Strips prefixes when returning files to frontend
|
| 15 |
-
- Supports CORS for cross-origin requests
|
| 16 |
-
|
| 17 |
-
**Endpoints**:
|
| 18 |
-
- `POST /api/mcp-handler` - Save/delete files, deploy quizzes
|
| 19 |
-
- `GET /api/mcp-handler?sessionId=...` - Retrieve files for a session
|
| 20 |
-
|
| 21 |
-
### 2. MCP Server (`mcp-server.js`)
|
| 22 |
-
- **Location**: `/mcp-server.js`
|
| 23 |
-
- **Purpose**: Bridge between Claude Desktop and Reuben OS
|
| 24 |
-
- **Tools**:
|
| 25 |
-
1. `manage_files` - Save, retrieve, delete, or clear files
|
| 26 |
-
2. `deploy_quiz` - Deploy interactive quizzes
|
| 27 |
-
|
| 28 |
-
### 3. Frontend Components (`frontend-fetch-example.js`)
|
| 29 |
-
- **Location**: `/frontend-fetch-example.js`
|
| 30 |
-
- **Components**:
|
| 31 |
-
- `SessionManager` - Main component for file management
|
| 32 |
-
- `FileItem` - Individual file display
|
| 33 |
-
- `FlutterAppViewer` - Flutter/Dart file viewer
|
| 34 |
-
- `LaTeXViewer` - LaTeX document viewer
|
| 35 |
-
|
| 36 |
-
## Complete Workflow
|
| 37 |
-
|
| 38 |
-
### Step 1: User Opens Reuben OS
|
| 39 |
-
1. Frontend generates a session ID: `session_{timestamp}_{random}`
|
| 40 |
-
2. Session ID is displayed in the UI
|
| 41 |
-
3. Frontend starts polling `/api/mcp-handler?sessionId=...` every 5 seconds
|
| 42 |
-
|
| 43 |
-
### Step 2: User Copies Session ID
|
| 44 |
-
1. User clicks "Copy ID" button in the UI
|
| 45 |
-
2. Session ID is copied to clipboard
|
| 46 |
-
|
| 47 |
-
### Step 3: User Provides Session ID to Claude
|
| 48 |
-
```
|
| 49 |
-
User: "My session ID is session_1234567_abc123"
|
| 50 |
-
Claude: "Got it! I'll use this session ID for all file operations."
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
### Step 4: Claude Saves Files
|
| 54 |
-
Claude uses the `manage_files` tool:
|
| 55 |
-
```javascript
|
| 56 |
-
{
|
| 57 |
-
sessionId: "session_1234567_abc123",
|
| 58 |
-
action: "save",
|
| 59 |
-
fileName: "main.dart",
|
| 60 |
-
content: "// Flutter code here..."
|
| 61 |
-
}
|
| 62 |
-
```
|
| 63 |
-
|
| 64 |
-
Backend saves as: `/tmp/session_1234567_abc123_main.dart`
|
| 65 |
-
|
| 66 |
-
### Step 5: Frontend Retrieves Files
|
| 67 |
-
Frontend polls: `GET /api/mcp-handler?sessionId=session_1234567_abc123`
|
| 68 |
-
|
| 69 |
-
API response:
|
| 70 |
-
```javascript
|
| 71 |
-
{
|
| 72 |
-
success: true,
|
| 73 |
-
sessionId: "session_1234567_abc123",
|
| 74 |
-
files: [
|
| 75 |
-
{
|
| 76 |
-
name: "main.dart", // Prefix stripped!
|
| 77 |
-
size: 1234,
|
| 78 |
-
content: "// Flutter code here...",
|
| 79 |
-
isQuiz: false
|
| 80 |
-
}
|
| 81 |
-
],
|
| 82 |
-
count: 1
|
| 83 |
-
}
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
### Step 6: Quiz Deployment
|
| 87 |
-
Claude deploys a quiz:
|
| 88 |
-
```javascript
|
| 89 |
-
{
|
| 90 |
-
sessionId: "session_1234567_abc123",
|
| 91 |
-
quizData: {
|
| 92 |
-
title: "JavaScript Quiz",
|
| 93 |
-
questions: [...]
|
| 94 |
-
}
|
| 95 |
-
}
|
| 96 |
-
```
|
| 97 |
-
|
| 98 |
-
Backend saves as: `/tmp/session_1234567_abc123_quiz.json`
|
| 99 |
-
Frontend detects `quiz.json` and shows "Launch Quiz" button.
|
| 100 |
-
|
| 101 |
-
## Testing Instructions
|
| 102 |
-
|
| 103 |
-
### 1. Start the Next.js Server
|
| 104 |
-
```bash
|
| 105 |
-
npm run dev
|
| 106 |
-
# Server runs on http://localhost:3000
|
| 107 |
-
```
|
| 108 |
-
|
| 109 |
-
### 2. Test API Directly
|
| 110 |
-
```bash
|
| 111 |
-
# Save a file
|
| 112 |
-
curl -X POST http://localhost:3000/api/mcp-handler \
|
| 113 |
-
-H "Content-Type: application/json" \
|
| 114 |
-
-d '{
|
| 115 |
-
"sessionId": "test123",
|
| 116 |
-
"action": "save_file",
|
| 117 |
-
"fileName": "test.txt",
|
| 118 |
-
"content": "Hello World"
|
| 119 |
-
}'
|
| 120 |
-
|
| 121 |
-
# Retrieve files
|
| 122 |
-
curl "http://localhost:3000/api/mcp-handler?sessionId=test123"
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
### 3. Configure MCP Server in Claude Desktop
|
| 126 |
-
Add to Claude Desktop settings (`claude_desktop_config.json`):
|
| 127 |
-
```json
|
| 128 |
-
{
|
| 129 |
-
"mcpServers": {
|
| 130 |
-
"reubenos": {
|
| 131 |
-
"command": "node",
|
| 132 |
-
"args": ["path/to/mcp-server.js"],
|
| 133 |
-
"env": {
|
| 134 |
-
"REUBENOS_URL": "http://localhost:3000"
|
| 135 |
-
}
|
| 136 |
-
}
|
| 137 |
-
}
|
| 138 |
-
}
|
| 139 |
-
```
|
| 140 |
-
|
| 141 |
-
### 4. Test with Claude
|
| 142 |
-
1. Open Claude Desktop
|
| 143 |
-
2. Tell Claude your session ID
|
| 144 |
-
3. Ask Claude to save a Flutter app:
|
| 145 |
-
```
|
| 146 |
-
"Save a Flutter counter app to my session: session_test123"
|
| 147 |
-
```
|
| 148 |
-
4. Check the frontend - file should appear immediately
|
| 149 |
-
|
| 150 |
-
## File Types Supported
|
| 151 |
-
|
| 152 |
-
- **Flutter/Dart**: `.dart` files - Can be opened in Zapp
|
| 153 |
-
- **LaTeX**: `.tex` files - Can be edited in LaTeX Studio
|
| 154 |
-
- **Quizzes**: `quiz.json` - Launches Quiz App
|
| 155 |
-
- **Any text file**: `.js`, `.py`, `.txt`, etc.
|
| 156 |
-
|
| 157 |
-
## Security Considerations
|
| 158 |
-
|
| 159 |
-
1. **Session ID Validation**: Only alphanumeric + hyphens/underscores
|
| 160 |
-
2. **Filename Sanitization**: Prevents path traversal attacks
|
| 161 |
-
3. **File Size Limits**: 1MB limit for content retrieval
|
| 162 |
-
4. **Temporary Storage**: Files in `/tmp` are ephemeral
|
| 163 |
-
5. **No Authentication**: Sessions are isolated by ID only
|
| 164 |
-
|
| 165 |
-
## Deployment on Hugging Face Spaces
|
| 166 |
-
|
| 167 |
-
1. **Environment**: Hugging Face provides `/tmp` for temporary storage
|
| 168 |
-
2. **Persistence**: `/tmp` is cleared on container restart
|
| 169 |
-
3. **Public Files**: Use `./public/uploads` for permanent storage
|
| 170 |
-
4. **CORS**: API allows all origins (adjust for production)
|
| 171 |
-
|
| 172 |
-
## Troubleshooting
|
| 173 |
-
|
| 174 |
-
### Files Not Appearing
|
| 175 |
-
- Check session ID matches exactly
|
| 176 |
-
- Verify API is accessible at configured URL
|
| 177 |
-
- Check browser console for errors
|
| 178 |
-
- Ensure `/tmp` directory exists and is writable
|
| 179 |
-
|
| 180 |
-
### MCP Connection Issues
|
| 181 |
-
- Verify `REUBENOS_URL` environment variable
|
| 182 |
-
- Check Claude Desktop has MCP server configured
|
| 183 |
-
- Look at MCP server console output for errors
|
| 184 |
-
|
| 185 |
-
### Quiz Not Launching
|
| 186 |
-
- Ensure quiz.json has correct structure
|
| 187 |
-
- Check frontend detects `isQuiz: true` flag
|
| 188 |
-
- Verify Quiz App route exists
|
| 189 |
-
|
| 190 |
-
## Example Session
|
| 191 |
-
|
| 192 |
-
1. **Frontend**: Generates `session_1732255800000_xyz789`
|
| 193 |
-
2. **User**: Copies ID, gives to Claude
|
| 194 |
-
3. **Claude**: Saves `main.dart` using manage_files tool
|
| 195 |
-
4. **Backend**: Stores as `/tmp/session_1732255800000_xyz789_main.dart`
|
| 196 |
-
5. **Frontend**: Polls and displays "main.dart" (prefix stripped)
|
| 197 |
-
6. **User**: Opens file in Zapp runner
|
| 198 |
-
7. **Claude**: Deploys quiz using deploy_quiz tool
|
| 199 |
-
8. **Frontend**: Shows quiz alert, launches Quiz App
|
| 200 |
-
|
| 201 |
-
## Summary
|
| 202 |
-
|
| 203 |
-
This implementation provides:
|
| 204 |
-
- Simple, stateless session management
|
| 205 |
-
- Automatic file isolation by session
|
| 206 |
-
- No complex state tracking
|
| 207 |
-
- Easy to debug and maintain
|
| 208 |
-
- Compatible with Hugging Face Spaces constraints
|
| 209 |
-
- Supports multiple file types and applications
|
| 210 |
-
|
| 211 |
-
The prefix strategy ensures users never see the technical details - they only see clean filenames while the system maintains perfect isolation behind the scenes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README-HF.md
DELETED
|
@@ -1,358 +0,0 @@
|
|
| 1 |
-
# ReubenOS - AI Desktop Environment with MCP Integration
|
| 2 |
-
|
| 3 |
-
[](https://huggingface.co/spaces)
|
| 4 |
-
[](https://www.docker.com/)
|
| 5 |
-
[](https://nextjs.org/)
|
| 6 |
-
[](LICENSE)
|
| 7 |
-
|
| 8 |
-
## π Overview
|
| 9 |
-
|
| 10 |
-
**ReubenOS** is a powerful AI-powered desktop environment that runs entirely in your browser, featuring:
|
| 11 |
-
|
| 12 |
-
- π₯οΈ **Full Desktop Experience**: macOS-inspired interface with draggable windows, dock, and desktop icons
|
| 13 |
-
- π **Advanced File Management**: Upload, organize, and manage files with session-based isolation
|
| 14 |
-
- π **Document Processing**: Generate and process DOCX, PDF, PowerPoint, Excel, and LaTeX documents
|
| 15 |
-
- π€ **AI Integration**: Built-in Gemini AI chat with voice transcription
|
| 16 |
-
- π **MCP Server**: Model Context Protocol server for seamless AI assistant integration
|
| 17 |
-
- π **Web Browser**: Built-in browser for web access
|
| 18 |
-
- π¬ **Real-time Collaboration**: Session-based file sharing and management
|
| 19 |
-
|
| 20 |
-
## π Live Demo
|
| 21 |
-
|
| 22 |
-
Try ReubenOS now: **[Launch ReubenOS on Hugging Face Spaces](https://huggingface.co/spaces/YOUR-USERNAME/reubenos)**
|
| 23 |
-
|
| 24 |
-
## π Features
|
| 25 |
-
|
| 26 |
-
### Desktop Environment
|
| 27 |
-
- **Window Management**: Multiple resizable, draggable windows
|
| 28 |
-
- **File Manager**: Intuitive file browser with drag-and-drop support
|
| 29 |
-
- **Terminal**: Command-line interface for advanced operations
|
| 30 |
-
- **Calendar & Clock**: Stay organized with built-in productivity tools
|
| 31 |
-
- **Spotlight Search**: Quick access to applications and files
|
| 32 |
-
|
| 33 |
-
### File Operations
|
| 34 |
-
- **Session Management**: Isolated workspaces for different projects
|
| 35 |
-
- **Public & Private Storage**: Share files publicly or keep them private
|
| 36 |
-
- **Document Generation**: Create professional documents programmatically
|
| 37 |
-
- **Document Processing**: Extract and analyze content from various formats
|
| 38 |
-
- **Secure Access**: Session-key based authentication
|
| 39 |
-
|
| 40 |
-
### MCP Server Integration
|
| 41 |
-
|
| 42 |
-
The MCP (Model Context Protocol) server allows AI assistants like Claude to interact with your files:
|
| 43 |
-
|
| 44 |
-
**Available Tools:**
|
| 45 |
-
- `create_session` - Create isolated workspaces
|
| 46 |
-
- `upload_file` - Upload files to sessions or public folders
|
| 47 |
-
- `download_file` - Retrieve files from storage
|
| 48 |
-
- `list_files` - Browse available files
|
| 49 |
-
- `generate_document` - Create DOCX, PDF, PowerPoint, Excel, or LaTeX documents
|
| 50 |
-
- `process_document` - Read and analyze documents
|
| 51 |
-
|
| 52 |
-
## π― Using ReubenOS on Hugging Face Spaces
|
| 53 |
-
|
| 54 |
-
### Accessing the Application
|
| 55 |
-
|
| 56 |
-
1. Visit your deployed Hugging Face Space
|
| 57 |
-
2. The desktop environment will load automatically
|
| 58 |
-
3. Click on icons to launch applications
|
| 59 |
-
4. Use the dock to manage running applications
|
| 60 |
-
|
| 61 |
-
### Creating a Session
|
| 62 |
-
|
| 63 |
-
Sessions provide isolated workspaces for your files:
|
| 64 |
-
|
| 65 |
-
1. Open the **File Manager** application
|
| 66 |
-
2. Click "New Session" or use the session manager
|
| 67 |
-
3. Save your **Session Key** - you'll need it for MCP access
|
| 68 |
-
|
| 69 |
-
### Uploading Files
|
| 70 |
-
|
| 71 |
-
**Via Web Interface:**
|
| 72 |
-
- Drag and drop files into the File Manager
|
| 73 |
-
- Use the upload button in any application
|
| 74 |
-
|
| 75 |
-
**Via MCP Server:**
|
| 76 |
-
```typescript
|
| 77 |
-
// Using the MCP server from Claude Desktop or other clients
|
| 78 |
-
{
|
| 79 |
-
"tool": "upload_file",
|
| 80 |
-
"sessionKey": "your-session-key",
|
| 81 |
-
"fileName": "document.pdf",
|
| 82 |
-
"content": "base64-encoded-content",
|
| 83 |
-
"isPublic": false
|
| 84 |
-
}
|
| 85 |
-
```
|
| 86 |
-
|
| 87 |
-
### Connecting Claude Desktop to Your Space
|
| 88 |
-
|
| 89 |
-
**Important**: The MCP server runs locally and communicates with your deployed Hugging Face Space.
|
| 90 |
-
|
| 91 |
-
Configure Claude Desktop to use your deployed MCP server:
|
| 92 |
-
|
| 93 |
-
```json
|
| 94 |
-
{
|
| 95 |
-
"mcpServers": {
|
| 96 |
-
"reubenos": {
|
| 97 |
-
"command": "node",
|
| 98 |
-
"args": ["/path/to/your/local/mcp-server.js"],
|
| 99 |
-
"env": {
|
| 100 |
-
"REUBENOS_URL": "https://YOUR-USERNAME-reubenos.hf.space"
|
| 101 |
-
}
|
| 102 |
-
}
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
**Replace**:
|
| 108 |
-
- `/path/to/your/local/mcp-server.js` - Actual path to the MCP server file on your computer
|
| 109 |
-
- `YOUR-USERNAME` - Your Hugging Face username
|
| 110 |
-
- `reubenos` - Your Space name (if different)
|
| 111 |
-
|
| 112 |
-
**Example for Windows**:
|
| 113 |
-
```json
|
| 114 |
-
"args": ["C:\\Users\\YourName\\Downloads\\mcp-server.js"]
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
**Example for macOS/Linux**:
|
| 118 |
-
```json
|
| 119 |
-
"args": ["/Users/yourname/projects/reubenos/mcp-server.js"]
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
**Location of Claude Desktop config:**
|
| 123 |
-
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
| 124 |
-
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
| 125 |
-
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
| 126 |
-
|
| 127 |
-
**After configuration**:
|
| 128 |
-
1. Save the config file
|
| 129 |
-
2. Restart Claude Desktop
|
| 130 |
-
3. The MCP tools will appear in Claude's tool panel
|
| 131 |
-
|
| 132 |
-
## π Network & Port Configuration
|
| 133 |
-
|
| 134 |
-
### Port Information
|
| 135 |
-
|
| 136 |
-
**Internal (Docker Container)**:
|
| 137 |
-
- Port **7860** - Standard Hugging Face Spaces port
|
| 138 |
-
- Configured in `Dockerfile` with `EXPOSE 7860`
|
| 139 |
-
- The Next.js server binds to `0.0.0.0:7860`
|
| 140 |
-
|
| 141 |
-
**External (Public Access)**:
|
| 142 |
-
- Access via: `https://YOUR-USERNAME-reubenos.hf.space`
|
| 143 |
-
- **No port number needed** - Hugging Face proxy handles routing
|
| 144 |
-
- Automatic HTTPS via Hugging Face infrastructure
|
| 145 |
-
|
| 146 |
-
### Important URLs
|
| 147 |
-
|
| 148 |
-
| Purpose | URL Format | Example |
|
| 149 |
-
|---------|-----------|---------|
|
| 150 |
-
| Web Interface | `https://[username]-[space-name].hf.space` | `https://john-reubenos.hf.space` |
|
| 151 |
-
| MCP Server (local) | Uses web interface URL | `REUBENOS_URL=https://john-reubenos.hf.space` |
|
| 152 |
-
| API Endpoints | `https://[username]-[space-name].hf.space/api/*` | `https://john-reubenos.hf.space/api/health` |
|
| 153 |
-
|
| 154 |
-
**Note**: The MCP server runs **locally** on your machine and communicates with your deployed Space via HTTPS. No ports need to be exposed for MCP functionality.
|
| 155 |
-
|
| 156 |
-
## π§ Deploying to Hugging Face Spaces
|
| 157 |
-
|
| 158 |
-
### Prerequisites
|
| 159 |
-
|
| 160 |
-
- Hugging Face account ([Sign up here](https://huggingface.co/join))
|
| 161 |
-
- Git installed on your local machine
|
| 162 |
-
- Node.js 20+ (for local MCP server)
|
| 163 |
-
|
| 164 |
-
### Deployment Steps
|
| 165 |
-
|
| 166 |
-
1. **Create a New Space**
|
| 167 |
-
- Go to [Hugging Face Spaces](https://huggingface.co/new-space)
|
| 168 |
-
- Select **Docker** as the Space SDK
|
| 169 |
-
- Choose a name for your Space (e.g., `reubenos`)
|
| 170 |
-
- Select **Public** or **Private** visibility
|
| 171 |
-
|
| 172 |
-
2. **Clone and Push**
|
| 173 |
-
```bash
|
| 174 |
-
# Clone this repository
|
| 175 |
-
git clone https://github.com/YOUR-REPO/reubenos.git
|
| 176 |
-
cd reubenos
|
| 177 |
-
|
| 178 |
-
# Add your Hugging Face Space as remote
|
| 179 |
-
git remote add hf https://huggingface.co/spaces/YOUR-USERNAME/reubenos
|
| 180 |
-
|
| 181 |
-
# Push to Hugging Face
|
| 182 |
-
git push hf main
|
| 183 |
-
```
|
| 184 |
-
|
| 185 |
-
3. **Configure Environment Variables** (Optional)
|
| 186 |
-
|
| 187 |
-
In your Space settings, add:
|
| 188 |
-
- `GEMINI_API_KEY` - For AI chat functionality
|
| 189 |
-
- `NODE_ENV` - Set to `production`
|
| 190 |
-
|
| 191 |
-
4. **Wait for Build**
|
| 192 |
-
- Hugging Face will automatically build your Docker container
|
| 193 |
-
- Build typically takes 5-10 minutes
|
| 194 |
-
- Check the logs tab for progress
|
| 195 |
-
|
| 196 |
-
5. **Access Your Space**
|
| 197 |
-
- Once built, your Space will be available at:
|
| 198 |
-
- `https://YOUR-USERNAME-reubenos.hf.space`
|
| 199 |
-
|
| 200 |
-
### Post-Deployment Configuration
|
| 201 |
-
|
| 202 |
-
**Update MCP Server URL:**
|
| 203 |
-
|
| 204 |
-
After deployment, update your local MCP configuration to point to your Hugging Face Space:
|
| 205 |
-
|
| 206 |
-
```json
|
| 207 |
-
{
|
| 208 |
-
"mcpServers": {
|
| 209 |
-
"reubenos": {
|
| 210 |
-
"command": "node",
|
| 211 |
-
"args": ["/path/to/mcp-server.js"],
|
| 212 |
-
"env": {
|
| 213 |
-
"REUBENOS_URL": "https://YOUR-USERNAME-reubenos.hf.space"
|
| 214 |
-
}
|
| 215 |
-
}
|
| 216 |
-
}
|
| 217 |
-
}
|
| 218 |
-
```
|
| 219 |
-
|
| 220 |
-
## π οΈ Development
|
| 221 |
-
|
| 222 |
-
### Local Development
|
| 223 |
-
|
| 224 |
-
```bash
|
| 225 |
-
# Install dependencies
|
| 226 |
-
npm install
|
| 227 |
-
|
| 228 |
-
# Run development server
|
| 229 |
-
npm run dev
|
| 230 |
-
|
| 231 |
-
# Build for production
|
| 232 |
-
npm run build
|
| 233 |
-
|
| 234 |
-
# Run production server
|
| 235 |
-
npm start
|
| 236 |
-
|
| 237 |
-
# Run MCP server locally
|
| 238 |
-
npm run mcp
|
| 239 |
-
```
|
| 240 |
-
|
| 241 |
-
### Project Structure
|
| 242 |
-
|
| 243 |
-
```
|
| 244 |
-
reubenos/
|
| 245 |
-
βββ app/ # Next.js app directory
|
| 246 |
-
β βββ api/ # API routes
|
| 247 |
-
β β βββ documents/ # Document processing endpoints
|
| 248 |
-
β β βββ sessions/ # Session management
|
| 249 |
-
β β βββ gemini/ # AI chat endpoints
|
| 250 |
-
β β βββ health/ # Health check endpoint
|
| 251 |
-
β βββ components/ # React components
|
| 252 |
-
β βββ page.tsx # Main desktop page
|
| 253 |
-
βββ backend/ # Python MCP server (alternative)
|
| 254 |
-
βββ mcp-server.js # Node.js MCP server
|
| 255 |
-
βββ public/ # Static assets
|
| 256 |
-
βββ Dockerfile # Docker configuration for HF Spaces
|
| 257 |
-
βββ next.config.mjs # Next.js configuration
|
| 258 |
-
βββ package.json # Node.js dependencies
|
| 259 |
-
```
|
| 260 |
-
|
| 261 |
-
### Environment Variables
|
| 262 |
-
|
| 263 |
-
Create a `.env.local` file for local development:
|
| 264 |
-
|
| 265 |
-
```env
|
| 266 |
-
GEMINI_API_KEY=your-gemini-api-key
|
| 267 |
-
REUBENOS_URL=http://localhost:3000
|
| 268 |
-
NODE_ENV=development
|
| 269 |
-
```
|
| 270 |
-
|
| 271 |
-
## π Security
|
| 272 |
-
|
| 273 |
-
### Session Management
|
| 274 |
-
- Each session has a unique key for authentication
|
| 275 |
-
- Sessions are isolated from each other
|
| 276 |
-
- Public files are accessible without authentication
|
| 277 |
-
- Private files require valid session keys
|
| 278 |
-
|
| 279 |
-
### Best Practices
|
| 280 |
-
- Never share your session keys publicly
|
| 281 |
-
- Use public folders only for non-sensitive data
|
| 282 |
-
- Regularly clean up unused sessions
|
| 283 |
-
- Keep your environment variables secure
|
| 284 |
-
|
| 285 |
-
## π API Reference
|
| 286 |
-
|
| 287 |
-
### REST API Endpoints
|
| 288 |
-
|
| 289 |
-
**Health Check**
|
| 290 |
-
```
|
| 291 |
-
GET /api/health
|
| 292 |
-
```
|
| 293 |
-
|
| 294 |
-
**Session Management**
|
| 295 |
-
```
|
| 296 |
-
POST /api/sessions/create
|
| 297 |
-
GET /api/sessions/files
|
| 298 |
-
POST /api/sessions/upload
|
| 299 |
-
GET /api/sessions/download
|
| 300 |
-
```
|
| 301 |
-
|
| 302 |
-
**Document Operations**
|
| 303 |
-
```
|
| 304 |
-
POST /api/documents/generate
|
| 305 |
-
POST /api/documents/process
|
| 306 |
-
```
|
| 307 |
-
|
| 308 |
-
### MCP Server Protocol
|
| 309 |
-
|
| 310 |
-
The MCP server communicates using the [Model Context Protocol](https://modelcontextprotocol.io/) specification, enabling AI assistants to:
|
| 311 |
-
- Create and manage sessions
|
| 312 |
-
- Upload and download files
|
| 313 |
-
- Generate various document types
|
| 314 |
-
- Process and analyze documents
|
| 315 |
-
|
| 316 |
-
## π€ Contributing
|
| 317 |
-
|
| 318 |
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
| 319 |
-
|
| 320 |
-
1. Fork the repository
|
| 321 |
-
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
| 322 |
-
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
| 323 |
-
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
| 324 |
-
5. Open a Pull Request
|
| 325 |
-
|
| 326 |
-
## π License
|
| 327 |
-
|
| 328 |
-
This project is licensed under the MIT License - see the LICENSE file for details.
|
| 329 |
-
|
| 330 |
-
## π Acknowledgments
|
| 331 |
-
|
| 332 |
-
- Built with [Next.js](https://nextjs.org/)
|
| 333 |
-
- Desktop UI inspired by macOS
|
| 334 |
-
- MCP protocol by [Anthropic](https://www.anthropic.com/)
|
| 335 |
-
- AI capabilities powered by Google Gemini
|
| 336 |
-
- Hosted on [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 337 |
-
|
| 338 |
-
## π§ Support
|
| 339 |
-
|
| 340 |
-
- **Issues**: [GitHub Issues](https://github.com/YOUR-REPO/reubenos/issues)
|
| 341 |
-
- **Discussions**: [GitHub Discussions](https://github.com/YOUR-REPO/reubenos/discussions)
|
| 342 |
-
- **Email**: [email protected]
|
| 343 |
-
|
| 344 |
-
## πΊοΈ Roadmap
|
| 345 |
-
|
| 346 |
-
- [ ] Multi-user support
|
| 347 |
-
- [ ] Real-time collaboration
|
| 348 |
-
- [ ] Cloud storage integration
|
| 349 |
-
- [ ] Mobile responsive design
|
| 350 |
-
- [ ] Additional AI model support
|
| 351 |
-
- [ ] Plugin system for extensions
|
| 352 |
-
- [ ] Theming support
|
| 353 |
-
|
| 354 |
-
---
|
| 355 |
-
|
| 356 |
-
**Made with β€οΈ for the AI community**
|
| 357 |
-
|
| 358 |
-
*Deploy to Hugging Face Spaces and make AI-powered file management accessible to everyone!*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REFACTORING_SUMMARY.md
DELETED
|
@@ -1,185 +0,0 @@
|
|
| 1 |
-
# Session Management Refactoring - Complete
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
Successfully removed all session management functionality from Reuben OS and replaced it with a simpler **passkey-based secure storage** system.
|
| 5 |
-
|
| 6 |
-
## Changes Made
|
| 7 |
-
|
| 8 |
-
### 1. **Desktop.tsx**
|
| 9 |
-
- β
Removed all session-related state variables (`userSession`, `sessionKey`, `sessionInitialized`, etc.)
|
| 10 |
-
- β
Removed `SessionManagerWindow` component and its rendering
|
| 11 |
-
- β
Removed session initialization `useEffect` hook
|
| 12 |
-
- β
Removed "Sessions" desktop icon and dock entry
|
| 13 |
-
- β
Removed session-related imports
|
| 14 |
-
|
| 15 |
-
### 2. **FileManager.tsx**
|
| 16 |
-
- β
Complete refactor to support **two storage modes**:
|
| 17 |
-
- **Public Files**: Accessible to everyone, no authentication
|
| 18 |
-
- **Secure Data**: Protected by passkey authentication
|
| 19 |
-
- β
Added beautiful passkey modal with Lock icon
|
| 20 |
-
- β
Removed `sessionId` prop from component interface
|
| 21 |
-
- β
Updated API calls to use new `/api/data` endpoint with passkey
|
| 22 |
-
- β
Added Lock/Unlock button in toolbar for secure data
|
| 23 |
-
|
| 24 |
-
### 3. **FlutterRunner.tsx**
|
| 25 |
-
- β
Removed `sessionId` prop
|
| 26 |
-
- β
Removed auto-save to session functionality
|
| 27 |
-
- β
Removed "Syncing..." indicator
|
| 28 |
-
- β
Simplified to focus on code editing with DartPad
|
| 29 |
-
|
| 30 |
-
### 4. **LaTeXEditor.tsx**
|
| 31 |
-
- β
Removed `sessionId` prop
|
| 32 |
-
- β
Removed auto-save to session functionality
|
| 33 |
-
- β
Removed "Syncing..." indicator
|
| 34 |
-
|
| 35 |
-
### 5. **Component Deletions**
|
| 36 |
-
- β
Deleted `SessionManagerWindow.tsx`
|
| 37 |
-
- β
Deleted `SessionManager.tsx`
|
| 38 |
-
- β
Deleted `/app/sessions` page directory
|
| 39 |
-
- β
Deleted `/app/api/sessions` directory (all session APIs)
|
| 40 |
-
|
| 41 |
-
### 6. **New API Routes**
|
| 42 |
-
|
| 43 |
-
#### `/api/data` - Passkey-Based Secure Storage
|
| 44 |
-
```typescript
|
| 45 |
-
GET /api/data?key={passkey}&folder={folder} // List files
|
| 46 |
-
POST /api/data // Upload file (requires key in FormData)
|
| 47 |
-
DELETE /api/data?key={passkey}&path={path} // Delete file
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
**Features:**
|
| 51 |
-
- Files are organized by passkey in `public/data/{sanitized-key}/`
|
| 52 |
-
- Each passkey creates an isolated storage namespace
|
| 53 |
-
- Sanitized keys prevent directory traversal attacks
|
| 54 |
-
- Simple and secure without database complexity
|
| 55 |
-
|
| 56 |
-
### 7. **Refactored API Routes**
|
| 57 |
-
|
| 58 |
-
#### `/api/files/route.ts`
|
| 59 |
-
- Removed all session logic
|
| 60 |
-
- Now handles public file operations only
|
| 61 |
-
- Simplified folder creation and deletion
|
| 62 |
-
|
| 63 |
-
#### `/api/documents/generate/route.ts`
|
| 64 |
-
- Removed SessionManager dependency
|
| 65 |
-
- Added `key` parameter for secure document generation
|
| 66 |
-
- Supports both public and passkey-protected storage
|
| 67 |
-
|
| 68 |
-
#### `/api/documents/process/route.ts`
|
| 69 |
-
- Removed SessionManager dependency
|
| 70 |
-
- Added `key` parameter for secure document processing
|
| 71 |
-
- Reads files from public or passkey-protected storage
|
| 72 |
-
|
| 73 |
-
#### `/api/public/upload/route.ts`
|
| 74 |
-
- Removed SessionManager dependency
|
| 75 |
-
- Direct file system operations for public uploads
|
| 76 |
-
|
| 77 |
-
### 8. **File Organization**
|
| 78 |
-
|
| 79 |
-
**New Structure:**
|
| 80 |
-
```
|
| 81 |
-
public/
|
| 82 |
-
βββ data/
|
| 83 |
-
βββ public/ # Public files (no auth)
|
| 84 |
-
βββ {passkey-1}/ # User 1's secure files
|
| 85 |
-
βββ {passkey-2}/ # User 2's secure files
|
| 86 |
-
βββ ...
|
| 87 |
-
```
|
| 88 |
-
|
| 89 |
-
## User Experience
|
| 90 |
-
|
| 91 |
-
### For Public Files:
|
| 92 |
-
1. Open File Manager β Click "Public Files" in sidebar
|
| 93 |
-
2. Upload, browse, and manage files freely
|
| 94 |
-
3. No authentication required
|
| 95 |
-
|
| 96 |
-
### For Secure Files:
|
| 97 |
-
1. Open File Manager β Click "Secure Data" in sidebar
|
| 98 |
-
2. Enter your passkey in the modal
|
| 99 |
-
3. Files are isolated to your passkey
|
| 100 |
-
4. Click "Lock" button to lock and require re-authentication
|
| 101 |
-
5. Re-enter passkey to "refresh" or access newer files
|
| 102 |
-
|
| 103 |
-
## Benefits
|
| 104 |
-
|
| 105 |
-
### β
**Simplicity**
|
| 106 |
-
- No database required
|
| 107 |
-
- No session expiration to manage
|
| 108 |
-
- No complex session validation
|
| 109 |
-
|
| 110 |
-
### β
**Security**
|
| 111 |
-
- Files are isolated by passkey
|
| 112 |
-
- Passkey never exposed in URLs (passed via POST)
|
| 113 |
-
- Directory traversal protection
|
| 114 |
-
|
| 115 |
-
### β
**Flexibility**
|
| 116 |
-
- Multiple users can use different passkeys
|
| 117 |
-
- Each passkey creates isolated storage
|
| 118 |
-
- Easy to share files publicly when needed
|
| 119 |
-
|
| 120 |
-
### β
**Performance**
|
| 121 |
-
- Direct file system access
|
| 122 |
-
- No database queries
|
| 123 |
-
- Faster file operations
|
| 124 |
-
|
| 125 |
-
## Build Status
|
| 126 |
-
β
**Build Successful** - All TypeScript compilation passed
|
| 127 |
-
β
**No Errors** - Clean production build
|
| 128 |
-
β
**All Routes Working** - 24 API routes active
|
| 129 |
-
|
| 130 |
-
## Next Steps for Integration with Claude Desktop
|
| 131 |
-
|
| 132 |
-
The passkey system is designed to work seamlessly with Claude Desktop:
|
| 133 |
-
|
| 134 |
-
1. **Claude can use a specific passkey** to store/retrieve files for a user
|
| 135 |
-
2. **Files are filtered by passkey** automatically
|
| 136 |
-
3. **No session management complexity** - just pass the key
|
| 137 |
-
4. **Upload files**: Include `key` in FormData: `formData.append('key', userPasskey)`
|
| 138 |
-
5. **Fetch files**: `GET /api/data?key={passkey}&folder={path}`
|
| 139 |
-
|
| 140 |
-
## API Usage Examples
|
| 141 |
-
|
| 142 |
-
### Upload to Secure Storage
|
| 143 |
-
```javascript
|
| 144 |
-
const formData = new FormData();
|
| 145 |
-
formData.append('file', fileBlob);
|
| 146 |
-
formData.append('key', 'user-passkey-123');
|
| 147 |
-
formData.append('folder', 'documents');
|
| 148 |
-
|
| 149 |
-
await fetch('/api/data', { method: 'POST', body: formData });
|
| 150 |
-
```
|
| 151 |
-
|
| 152 |
-
### List Secure Files
|
| 153 |
-
```javascript
|
| 154 |
-
const response = await fetch('/api/data?key=user-passkey-123&folder=documents');
|
| 155 |
-
const { files } = await response.json();
|
| 156 |
-
```
|
| 157 |
-
|
| 158 |
-
### Generate Document to Secure Storage
|
| 159 |
-
```javascript
|
| 160 |
-
await fetch('/api/documents/generate', {
|
| 161 |
-
method: 'POST',
|
| 162 |
-
headers: { 'Content-Type': 'application/json' },
|
| 163 |
-
body: JSON.stringify({
|
| 164 |
-
type: 'pdf',
|
| 165 |
-
fileName: 'report.pdf',
|
| 166 |
-
key: 'user-passkey-123',
|
| 167 |
-
isPublic: false,
|
| 168 |
-
content: { title: 'Report', content: '...' }
|
| 169 |
-
})
|
| 170 |
-
});
|
| 171 |
-
```
|
| 172 |
-
|
| 173 |
-
## Summary
|
| 174 |
-
|
| 175 |
-
The refactoring successfully:
|
| 176 |
-
- β
Removed all session management complexity
|
| 177 |
-
- β
Implemented passkey-based secure storage
|
| 178 |
-
- β
Maintained public file sharing capability
|
| 179 |
-
- β
Created clean, simple API surface
|
| 180 |
-
- β
Built successfully with no errors
|
| 181 |
-
- β
Ready for Claude Desktop integration
|
| 182 |
-
|
| 183 |
-
**Total Files Modified:** 15+
|
| 184 |
-
**Lines of Code Removed:** ~2000+ (session management complexity)
|
| 185 |
-
**New Features:** Passkey modal, secure storage API, lock/unlock UI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VOICE_STUDIO_COMPLETE.md
DELETED
|
@@ -1,129 +0,0 @@
|
|
| 1 |
-
# π Voice Studio App - Complete!
|
| 2 |
-
|
| 3 |
-
## What You Can Do Now
|
| 4 |
-
|
| 5 |
-
### β¨ With Claude Desktop
|
| 6 |
-
|
| 7 |
-
**Generate Songs:**
|
| 8 |
-
```
|
| 9 |
-
"Generate a romantic ballad about love and create audio"
|
| 10 |
-
"Write rock lyrics about adventure and make it a song"
|
| 11 |
-
"Create a jazz piece about rainy days with audio"
|
| 12 |
-
```
|
| 13 |
-
|
| 14 |
-
**Generate Stories:**
|
| 15 |
-
```
|
| 16 |
-
"Write a fantasy story and narrate it"
|
| 17 |
-
"Tell a sci-fi tale about space and create audio"
|
| 18 |
-
"Make a bedtime story with narration"
|
| 19 |
-
```
|
| 20 |
-
|
| 21 |
-
### π¨ Voice Studio App Features
|
| 22 |
-
|
| 23 |
-
- **Beautiful UI** with purple/pink gradients
|
| 24 |
-
- **Play/Stop** audio controls
|
| 25 |
-
- **Auto-refresh** every 5 seconds
|
| 26 |
-
- **Delete** unwanted content
|
| 27 |
-
- **Persistent storage** across sessions
|
| 28 |
-
|
| 29 |
-
## π¦ What Was Built
|
| 30 |
-
|
| 31 |
-
### Components
|
| 32 |
-
1. **VoiceApp.tsx** - Main application
|
| 33 |
-
2. **Desktop integration** - Icon and window management
|
| 34 |
-
3. **Icon component** - Music note icon
|
| 35 |
-
|
| 36 |
-
### API Endpoints
|
| 37 |
-
1. `/api/voice/generate-song` - ElevenLabs Music API
|
| 38 |
-
2. `/api/voice/generate-story` - ElevenLabs TTS API
|
| 39 |
-
3. `/api/voice/save` - Content storage
|
| 40 |
-
4. `/api/voice/check-new` - Polling endpoint
|
| 41 |
-
|
| 42 |
-
### MCP Tools
|
| 43 |
-
1. `generate_song_audio` - For Claude to create songs
|
| 44 |
-
2. `generate_story_audio` - For Claude to narrate stories
|
| 45 |
-
|
| 46 |
-
## π Next Steps
|
| 47 |
-
|
| 48 |
-
### 1. Set Up ElevenLabs API Key
|
| 49 |
-
|
| 50 |
-
```bash
|
| 51 |
-
# Get your API key from https://elevenlabs.io
|
| 52 |
-
export ELEVENLABS_API_KEY="your_key_here"
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
Or add to `.env.local`:
|
| 56 |
-
```
|
| 57 |
-
ELEVENLABS_API_KEY=your_key_here
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### 2. Restart Server
|
| 61 |
-
|
| 62 |
-
```bash
|
| 63 |
-
npm run dev
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
### 3. Test It Out!
|
| 67 |
-
|
| 68 |
-
**Option A: Via Claude Desktop**
|
| 69 |
-
1. Open Claude Desktop
|
| 70 |
-
2. Say: "Generate a happy birthday song in pop style"
|
| 71 |
-
3. Open Voice Studio app to listen
|
| 72 |
-
|
| 73 |
-
**Option B: Test Script**
|
| 74 |
-
```bash
|
| 75 |
-
node test-voice-studio.js
|
| 76 |
-
```
|
| 77 |
-
|
| 78 |
-
**Option C: Manual Test**
|
| 79 |
-
1. Double-click "Voice Studio" icon
|
| 80 |
-
2. App opens (empty at first)
|
| 81 |
-
3. Use Claude to generate content
|
| 82 |
-
4. Content appears automatically!
|
| 83 |
-
|
| 84 |
-
## π Documentation
|
| 85 |
-
|
| 86 |
-
- **VOICE_STUDIO_GUIDE.md** - Complete technical guide
|
| 87 |
-
- **VOICE_STUDIO_IMPLEMENTATION.md** - Implementation details
|
| 88 |
-
- **VOICE_STUDIO_QUICK_START.md** - Quick reference
|
| 89 |
-
|
| 90 |
-
## π― Example Workflow
|
| 91 |
-
|
| 92 |
-
```
|
| 93 |
-
You: "Claude, create a song about summer"
|
| 94 |
-
|
| 95 |
-
Claude: *writes lyrics*
|
| 96 |
-
*calls generate_song_audio tool*
|
| 97 |
-
β
"Song generated! Open Voice Studio to listen"
|
| 98 |
-
|
| 99 |
-
You: *opens Voice Studio app*
|
| 100 |
-
*sees "Summer Vibes" song card*
|
| 101 |
-
*clicks Play*
|
| 102 |
-
π΅ *music plays*
|
| 103 |
-
```
|
| 104 |
-
|
| 105 |
-
## β
Success Checklist
|
| 106 |
-
|
| 107 |
-
- [x] Voice Studio app created
|
| 108 |
-
- [x] ElevenLabs song generation working
|
| 109 |
-
- [x] ElevenLabs story narration working
|
| 110 |
-
- [x] Desktop icon added
|
| 111 |
-
- [x] MCP tools integrated
|
| 112 |
-
- [x] Storage system implemented
|
| 113 |
-
- [x] Auto-refresh working
|
| 114 |
-
- [x] Documentation complete
|
| 115 |
-
|
| 116 |
-
## π You're All Set!
|
| 117 |
-
|
| 118 |
-
The Voice Studio app is **fully functional** and ready to use!
|
| 119 |
-
|
| 120 |
-
Just remember to:
|
| 121 |
-
1. β
Set your ELEVENLABS_API_KEY
|
| 122 |
-
2. β
Have some ElevenLabs credits
|
| 123 |
-
3. β
Use a passkey when generating content
|
| 124 |
-
|
| 125 |
-
**Happy creating! π΅π**
|
| 126 |
-
|
| 127 |
-
---
|
| 128 |
-
|
| 129 |
-
*For questions or issues, check the documentation files or Claude's help.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VOICE_STUDIO_GUIDE.md
DELETED
|
@@ -1,233 +0,0 @@
|
|
| 1 |
-
# Voice Studio App - ElevenLabs Integration Guide
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
|
| 5 |
-
The Voice Studio app allows users to generate AI-powered audio content using ElevenLabs API. Users can create:
|
| 6 |
-
1. **Songs** - Generate music with lyrics and custom styles
|
| 7 |
-
2. **Stories** - Convert written stories into narrated audio
|
| 8 |
-
|
| 9 |
-
## Setup
|
| 10 |
-
|
| 11 |
-
### 1. ElevenLabs API Key
|
| 12 |
-
|
| 13 |
-
Set your ElevenLabs API key as an environment variable:
|
| 14 |
-
|
| 15 |
-
```bash
|
| 16 |
-
export ELEVENLABS_API_KEY="your_api_key_here"
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
Or add it to your `.env.local` file:
|
| 20 |
-
|
| 21 |
-
```
|
| 22 |
-
ELEVENLABS_API_KEY=your_api_key_here
|
| 23 |
-
```
|
| 24 |
-
|
| 25 |
-
Get your API key from: https://elevenlabs.io/app/settings/api-keys
|
| 26 |
-
|
| 27 |
-
### 2. Install Dependencies
|
| 28 |
-
|
| 29 |
-
The required dependencies are already in `package.json`. If you need to reinstall:
|
| 30 |
-
|
| 31 |
-
```bash
|
| 32 |
-
npm install
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
## Usage
|
| 36 |
-
|
| 37 |
-
### Via Claude Desktop (MCP)
|
| 38 |
-
|
| 39 |
-
Claude can now generate audio content directly using these tools:
|
| 40 |
-
|
| 41 |
-
#### 1. Generate Song Audio
|
| 42 |
-
|
| 43 |
-
**Example prompt:**
|
| 44 |
-
```
|
| 45 |
-
Generate lyrics for a romantic ballad about love under the stars and create audio for it.
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
Claude will:
|
| 49 |
-
1. Create song lyrics
|
| 50 |
-
2. Define a musical style
|
| 51 |
-
3. Call `generate_song_audio` tool
|
| 52 |
-
4. Save the audio to Voice Studio
|
| 53 |
-
|
| 54 |
-
**MCP Tool Parameters:**
|
| 55 |
-
- `title`: Song title
|
| 56 |
-
- `style`: Musical genre (e.g., "pop", "rock", "jazz", "ballad")
|
| 57 |
-
- `lyrics`: The song lyrics
|
| 58 |
-
- `passkey`: Your authentication passkey
|
| 59 |
-
|
| 60 |
-
#### 2. Generate Story Audio
|
| 61 |
-
|
| 62 |
-
**Example prompt:**
|
| 63 |
-
```
|
| 64 |
-
Write a short sci-fi story about space exploration and generate audio narration for it.
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
Claude will:
|
| 68 |
-
1. Write the story
|
| 69 |
-
2. Call `generate_story_audio` tool
|
| 70 |
-
3. Save the narrated audio to Voice Studio
|
| 71 |
-
|
| 72 |
-
**MCP Tool Parameters:**
|
| 73 |
-
- `title`: Story title
|
| 74 |
-
- `content`: Story text (max 2000 characters for best performance)
|
| 75 |
-
- `passkey`: Your authentication passkey
|
| 76 |
-
|
| 77 |
-
### Via Desktop App
|
| 78 |
-
|
| 79 |
-
1. **Open Voice Studio**
|
| 80 |
-
- Double-click the "Voice Studio" icon on desktop
|
| 81 |
-
- Or ask Claude to generate content (it will appear automatically)
|
| 82 |
-
|
| 83 |
-
2. **Listen to Content**
|
| 84 |
-
- Click the "Play" button on any generated content
|
| 85 |
-
- Click again to stop playback
|
| 86 |
-
|
| 87 |
-
3. **Delete Content**
|
| 88 |
-
- Click the trash icon to remove unwanted items
|
| 89 |
-
|
| 90 |
-
4. **Refresh**
|
| 91 |
-
- Click "Refresh" button to check for new content
|
| 92 |
-
|
| 93 |
-
## API Endpoints
|
| 94 |
-
|
| 95 |
-
### Generate Song
|
| 96 |
-
`POST /api/voice/generate-song`
|
| 97 |
-
|
| 98 |
-
Request body:
|
| 99 |
-
```json
|
| 100 |
-
{
|
| 101 |
-
"title": "My Song",
|
| 102 |
-
"style": "pop rock",
|
| 103 |
-
"lyrics": "Song lyrics here...",
|
| 104 |
-
"passkey": "your_passkey"
|
| 105 |
-
}
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
### Generate Story
|
| 109 |
-
`POST /api/voice/generate-story`
|
| 110 |
-
|
| 111 |
-
Request body:
|
| 112 |
-
```json
|
| 113 |
-
{
|
| 114 |
-
"title": "My Story",
|
| 115 |
-
"content": "Story text here...",
|
| 116 |
-
"passkey": "your_passkey"
|
| 117 |
-
}
|
| 118 |
-
```
|
| 119 |
-
|
| 120 |
-
### Save Content
|
| 121 |
-
`POST /api/voice/save`
|
| 122 |
-
|
| 123 |
-
Request body:
|
| 124 |
-
```json
|
| 125 |
-
{
|
| 126 |
-
"passkey": "your_passkey",
|
| 127 |
-
"content": {...}
|
| 128 |
-
}
|
| 129 |
-
```
|
| 130 |
-
|
| 131 |
-
### Retrieve Content
|
| 132 |
-
`GET /api/voice/save?passkey=your_passkey`
|
| 133 |
-
|
| 134 |
-
## Features
|
| 135 |
-
|
| 136 |
-
### Audio Generation
|
| 137 |
-
- **Songs**: Uses ElevenLabs Music API to generate instrumental music based on lyrics and style
|
| 138 |
-
- **Stories**: Uses ElevenLabs Text-to-Speech API with natural voice (Bella voice by default)
|
| 139 |
-
|
| 140 |
-
### Storage
|
| 141 |
-
- Content is saved server-side keyed by passkey
|
| 142 |
-
- Also cached in localStorage for offline access
|
| 143 |
-
- Automatic sync every 5 seconds
|
| 144 |
-
|
| 145 |
-
### UI Features
|
| 146 |
-
- Play/Stop audio controls
|
| 147 |
-
- Progress indication during generation
|
| 148 |
-
- Beautiful gradient cards for each content item
|
| 149 |
-
- Delete functionality
|
| 150 |
-
- Auto-refresh
|
| 151 |
-
|
| 152 |
-
## Limitations
|
| 153 |
-
|
| 154 |
-
### ElevenLabs API Limits
|
| 155 |
-
- **Music Generation**: 30 seconds per request (configurable)
|
| 156 |
-
- **Text-to-Speech**: 2000 characters recommended per request
|
| 157 |
-
- **Rate Limits**: Depends on your ElevenLabs subscription plan
|
| 158 |
-
|
| 159 |
-
### Browser Limitations
|
| 160 |
-
- Large audio files are base64 encoded (may consume memory)
|
| 161 |
-
- Audio plays one at a time
|
| 162 |
-
|
| 163 |
-
## Troubleshooting
|
| 164 |
-
|
| 165 |
-
### "ElevenLabs API key not configured"
|
| 166 |
-
- Ensure `ELEVENLABS_API_KEY` is set in environment variables
|
| 167 |
-
- Restart the Next.js dev server after setting the variable
|
| 168 |
-
|
| 169 |
-
### "Failed to generate audio"
|
| 170 |
-
- Check your ElevenLabs API key is valid
|
| 171 |
-
- Verify you have sufficient API credits
|
| 172 |
-
- Check the console for detailed error messages
|
| 173 |
-
|
| 174 |
-
### Content not appearing
|
| 175 |
-
- Click the "Refresh" button
|
| 176 |
-
- Check that the passkey matches between generation and retrieval
|
| 177 |
-
- Verify content was saved successfully (check server logs)
|
| 178 |
-
|
| 179 |
-
## Example Workflow
|
| 180 |
-
|
| 181 |
-
1. **User opens Claude Desktop**
|
| 182 |
-
2. **User asks:** "Create lyrics for a happy birthday song in jazz style and generate audio"
|
| 183 |
-
3. **Claude:**
|
| 184 |
-
- Writes creative lyrics
|
| 185 |
-
- Calls `generate_song_audio` with lyrics and "jazz" style
|
| 186 |
-
- Returns success message
|
| 187 |
-
4. **User opens Voice Studio app**
|
| 188 |
-
5. **Audio appears** with:
|
| 189 |
-
- Title: "Happy Birthday Jazz"
|
| 190 |
-
- Style: "jazz"
|
| 191 |
-
- Lyrics displayed
|
| 192 |
-
- Play button ready
|
| 193 |
-
6. **User clicks Play** - Enjoys the generated music!
|
| 194 |
-
|
| 195 |
-
## Voice App Icon
|
| 196 |
-
|
| 197 |
-
The Voice Studio app icon features:
|
| 198 |
-
- Purple to pink gradient
|
| 199 |
-
- Music note symbol
|
| 200 |
-
- iOS-style rounded corners
|
| 201 |
-
- Appears on desktop and in dock when minimized
|
| 202 |
-
|
| 203 |
-
## Technical Details
|
| 204 |
-
|
| 205 |
-
### Component Structure
|
| 206 |
-
- `VoiceApp.tsx` - Main component
|
| 207 |
-
- `/api/voice/generate-song/route.ts` - Song generation endpoint
|
| 208 |
-
- `/api/voice/generate-story/route.ts` - Story generation endpoint
|
| 209 |
-
- `/api/voice/save/route.ts` - Content storage endpoint
|
| 210 |
-
- `mcp-server.js` - MCP tool definitions
|
| 211 |
-
|
| 212 |
-
### Data Flow
|
| 213 |
-
1. Claude calls MCP tool β
|
| 214 |
-
2. MCP server calls Next.js API β
|
| 215 |
-
3. API calls ElevenLabs β
|
| 216 |
-
4. Audio returned and saved β
|
| 217 |
-
5. Voice Studio app displays content
|
| 218 |
-
|
| 219 |
-
## Future Enhancements
|
| 220 |
-
|
| 221 |
-
- [ ] Multiple voice options for stories
|
| 222 |
-
- [ ] Longer music generation
|
| 223 |
-
- [ ] Download audio files
|
| 224 |
-
- [ ] Share audio via URL
|
| 225 |
-
- [ ] Playlist functionality
|
| 226 |
-
- [ ] Custom voice settings
|
| 227 |
-
- [ ] Audio waveform visualization
|
| 228 |
-
|
| 229 |
-
## Resources
|
| 230 |
-
|
| 231 |
-
- [ElevenLabs Documentation](https://elevenlabs.io/docs)
|
| 232 |
-
- [ElevenLabs Music API](https://elevenlabs.io/docs/api-reference/text-to-music)
|
| 233 |
-
- [ElevenLabs TTS API](https://elevenlabs.io/docs/api-reference/text-to-speech)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VOICE_STUDIO_IMPLEMENTATION.md
DELETED
|
@@ -1,276 +0,0 @@
|
|
| 1 |
-
# Voice Studio App - Implementation Summary
|
| 2 |
-
|
| 3 |
-
## β
What Was Implemented
|
| 4 |
-
|
| 5 |
-
### 1. **Voice Studio App Component** (`VoiceApp.tsx`)
|
| 6 |
-
- Beautiful UI with gradient purple/pink theme
|
| 7 |
-
- Displays songs and stories with audio playback
|
| 8 |
-
- Play/Stop controls for each item
|
| 9 |
-
- Delete functionality
|
| 10 |
-
- Auto-refresh every 5 seconds
|
| 11 |
-
- Loading states during audio generation
|
| 12 |
-
- Supports both server storage and localStorage
|
| 13 |
-
|
| 14 |
-
### 2. **API Routes**
|
| 15 |
-
Created 4 new API endpoints:
|
| 16 |
-
|
| 17 |
-
#### `/api/voice/generate-song/route.ts`
|
| 18 |
-
- Generates songs using ElevenLabs Music API
|
| 19 |
-
- Takes title, style, lyrics, and passkey
|
| 20 |
-
- Returns base64-encoded audio data
|
| 21 |
-
- 30-second music generation
|
| 22 |
-
|
| 23 |
-
#### `/api/voice/generate-story/route.ts`
|
| 24 |
-
- Converts stories to audio using ElevenLabs TTS
|
| 25 |
-
- Uses Bella voice (natural female voice)
|
| 26 |
-
- Supports up to 2000 characters
|
| 27 |
-
- Returns base64-encoded audio
|
| 28 |
-
|
| 29 |
-
#### `/api/voice/save/route.ts`
|
| 30 |
-
- Stores voice content keyed by passkey
|
| 31 |
-
- GET endpoint to retrieve content
|
| 32 |
-
- File-based storage in `data/voice-content/`
|
| 33 |
-
|
| 34 |
-
#### `/api/voice/check-new/route.ts`
|
| 35 |
-
- Polling endpoint (currently simple)
|
| 36 |
-
- Can be extended for real-time notifications
|
| 37 |
-
|
| 38 |
-
### 3. **MCP Server Integration** (`mcp-server.js`)
|
| 39 |
-
Added 2 new MCP tools that Claude can use:
|
| 40 |
-
|
| 41 |
-
#### `generate_song_audio`
|
| 42 |
-
- Parameters: title, style, lyrics, passkey
|
| 43 |
-
- Calls the song generation API
|
| 44 |
-
- Saves content to server storage
|
| 45 |
-
- Returns success message to Claude
|
| 46 |
-
|
| 47 |
-
#### `generate_story_audio`
|
| 48 |
-
- Parameters: title, content, passkey
|
| 49 |
-
- Calls the story generation API
|
| 50 |
-
- Saves content to server storage
|
| 51 |
-
- Returns success message to Claude
|
| 52 |
-
|
| 53 |
-
### 4. **Desktop Integration**
|
| 54 |
-
- Added Voice Studio icon to desktop (music note icon)
|
| 55 |
-
- Purple-to-pink gradient styling
|
| 56 |
-
- Added to dock when minimized
|
| 57 |
-
- Z-index management for window layering
|
| 58 |
-
- Open/close/minimize functionality
|
| 59 |
-
|
| 60 |
-
### 5. **Icon Component Updates** (`DraggableDesktopIcon.tsx`)
|
| 61 |
-
- Added `voice-app` icon type
|
| 62 |
-
- Consistent iOS-style design
|
| 63 |
-
- Hover effects and animations
|
| 64 |
-
|
| 65 |
-
## π― User Workflow
|
| 66 |
-
|
| 67 |
-
### Scenario 1: Generate a Song
|
| 68 |
-
```
|
| 69 |
-
User β Claude Desktop:
|
| 70 |
-
"Generate lyrics for a romantic ballad and create audio"
|
| 71 |
-
|
| 72 |
-
Claude:
|
| 73 |
-
1. Writes creative lyrics
|
| 74 |
-
2. Calls generate_song_audio tool
|
| 75 |
-
3. API calls ElevenLabs Music API
|
| 76 |
-
4. Audio saved to server
|
| 77 |
-
5. "β
Song generated! Open Voice Studio app"
|
| 78 |
-
|
| 79 |
-
User β Opens Voice Studio App:
|
| 80 |
-
- Sees new song card
|
| 81 |
-
- Clicks Play button
|
| 82 |
-
- Enjoys AI-generated music!
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
### Scenario 2: Generate a Story
|
| 86 |
-
```
|
| 87 |
-
User β Claude Desktop:
|
| 88 |
-
"Write a sci-fi story about Mars and narrate it"
|
| 89 |
-
|
| 90 |
-
Claude:
|
| 91 |
-
1. Writes engaging story
|
| 92 |
-
2. Calls generate_story_audio tool
|
| 93 |
-
3. API calls ElevenLabs TTS API
|
| 94 |
-
4. Audio saved to server
|
| 95 |
-
5. "β
Story audio generated! Open Voice Studio"
|
| 96 |
-
|
| 97 |
-
User β Opens Voice Studio App:
|
| 98 |
-
- Sees new story card
|
| 99 |
-
- Clicks Play button
|
| 100 |
-
- Listens to narrated story!
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
## π File Structure
|
| 104 |
-
|
| 105 |
-
```
|
| 106 |
-
app/
|
| 107 |
-
βββ components/
|
| 108 |
-
β βββ VoiceApp.tsx # Main Voice Studio component
|
| 109 |
-
β βββ Desktop.tsx # Updated with Voice App integration
|
| 110 |
-
β βββ DraggableDesktopIcon.tsx # Updated with voice-app icon
|
| 111 |
-
βββ api/
|
| 112 |
-
β βββ voice/
|
| 113 |
-
β βββ generate-song/route.ts # Song generation endpoint
|
| 114 |
-
β βββ generate-story/route.ts # Story generation endpoint
|
| 115 |
-
β βββ save/route.ts # Content storage endpoint
|
| 116 |
-
β βββ check-new/route.ts # Polling endpoint
|
| 117 |
-
data/
|
| 118 |
-
βββ voice-content/ # Voice content storage directory
|
| 119 |
-
βββ {passkey}.json # Per-user content files
|
| 120 |
-
mcp-server.js # Updated with voice generation tools
|
| 121 |
-
VOICE_STUDIO_GUIDE.md # Complete documentation
|
| 122 |
-
```
|
| 123 |
-
|
| 124 |
-
## π§ Configuration Required
|
| 125 |
-
|
| 126 |
-
### Environment Variables
|
| 127 |
-
Add to `.env.local`:
|
| 128 |
-
```env
|
| 129 |
-
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here
|
| 130 |
-
```
|
| 131 |
-
|
| 132 |
-
### Get ElevenLabs API Key
|
| 133 |
-
1. Sign up at https://elevenlabs.io
|
| 134 |
-
2. Go to Settings β API Keys
|
| 135 |
-
3. Create new API key
|
| 136 |
-
4. Copy and paste into `.env.local`
|
| 137 |
-
|
| 138 |
-
## π¨ UI Features
|
| 139 |
-
|
| 140 |
-
### Voice Content Cards
|
| 141 |
-
- **Song Cards**: Purple/pink gradient header with music note icon
|
| 142 |
-
- **Story Cards**: Blue/cyan gradient header with book icon
|
| 143 |
-
- **Display**: Title, style/genre, lyrics/content preview
|
| 144 |
-
- **Controls**: Play/Stop button, Delete button
|
| 145 |
-
- **Status**: Loading spinner during generation
|
| 146 |
-
|
| 147 |
-
### Empty State
|
| 148 |
-
- Helpful message with example prompts
|
| 149 |
-
- Beautiful gradient icon
|
| 150 |
-
- Encourages users to try the feature
|
| 151 |
-
|
| 152 |
-
### Desktop Icon
|
| 153 |
-
- Purple-to-pink gradient background
|
| 154 |
-
- White music note icon
|
| 155 |
-
- Rounded iOS-style corners
|
| 156 |
-
- Hover scale animation
|
| 157 |
-
|
| 158 |
-
## π§ͺ Testing Instructions
|
| 159 |
-
|
| 160 |
-
### 1. Setup Test
|
| 161 |
-
```bash
|
| 162 |
-
# Set API key
|
| 163 |
-
export ELEVENLABS_API_KEY="your_key"
|
| 164 |
-
|
| 165 |
-
# Restart dev server
|
| 166 |
-
npm run dev
|
| 167 |
-
```
|
| 168 |
-
|
| 169 |
-
### 2. Test via Claude Desktop
|
| 170 |
-
|
| 171 |
-
Open Claude Desktop and try:
|
| 172 |
-
|
| 173 |
-
**Test 1: Simple Song**
|
| 174 |
-
```
|
| 175 |
-
Generate lyrics for a happy birthday song in pop style and create audio with passkey "test123"
|
| 176 |
-
```
|
| 177 |
-
|
| 178 |
-
**Test 2: Story Narration**
|
| 179 |
-
```
|
| 180 |
-
Write a short bedtime story about a friendly dragon and generate audio narration with passkey "test123"
|
| 181 |
-
```
|
| 182 |
-
|
| 183 |
-
**Test 3: Custom Style**
|
| 184 |
-
```
|
| 185 |
-
Create a jazz song about rainy days with passkey "test123"
|
| 186 |
-
```
|
| 187 |
-
|
| 188 |
-
### 3. Test Voice Studio App
|
| 189 |
-
1. Open Reuben OS in browser
|
| 190 |
-
2. Double-click "Voice Studio" icon
|
| 191 |
-
3. Should see generated content
|
| 192 |
-
4. Click Play to test audio
|
| 193 |
-
5. Click Refresh to check for updates
|
| 194 |
-
|
| 195 |
-
## β οΈ Known Limitations
|
| 196 |
-
|
| 197 |
-
1. **Music Generation**: Limited to 30 seconds (can be increased in API)
|
| 198 |
-
2. **Story Length**: Recommended max 2000 characters for best performance
|
| 199 |
-
3. **Audio Storage**: Uses base64 encoding (memory intensive for long audio)
|
| 200 |
-
4. **Playback**: One audio at a time
|
| 201 |
-
5. **Rate Limits**: Depends on ElevenLabs subscription tier
|
| 202 |
-
|
| 203 |
-
## π Future Enhancements
|
| 204 |
-
|
| 205 |
-
### Short Term
|
| 206 |
-
- [ ] Audio download functionality
|
| 207 |
-
- [ ] Waveform visualization
|
| 208 |
-
- [ ] Multiple voice selection for stories
|
| 209 |
-
- [ ] Custom music duration
|
| 210 |
-
|
| 211 |
-
### Long Term
|
| 212 |
-
- [ ] Playlist creation
|
| 213 |
-
- [ ] Share audio via URL
|
| 214 |
-
- [ ] Audio editing capabilities
|
| 215 |
-
- [ ] Voice cloning integration
|
| 216 |
-
- [ ] Real-time generation progress
|
| 217 |
-
|
| 218 |
-
## π Troubleshooting
|
| 219 |
-
|
| 220 |
-
### Issue: "ElevenLabs API key not configured"
|
| 221 |
-
**Solution**:
|
| 222 |
-
1. Check `.env.local` has `ELEVENLABS_API_KEY`
|
| 223 |
-
2. Restart Next.js server: `npm run dev`
|
| 224 |
-
|
| 225 |
-
### Issue: Content not appearing in Voice Studio
|
| 226 |
-
**Solution**:
|
| 227 |
-
1. Click Refresh button
|
| 228 |
-
2. Check passkey matches
|
| 229 |
-
3. Open browser console for errors
|
| 230 |
-
4. Check server logs for API errors
|
| 231 |
-
|
| 232 |
-
### Issue: Audio fails to generate
|
| 233 |
-
**Solution**:
|
| 234 |
-
1. Verify API key is valid
|
| 235 |
-
2. Check ElevenLabs account has credits
|
| 236 |
-
3. Review API error messages in console
|
| 237 |
-
4. Try shorter text/lyrics
|
| 238 |
-
|
| 239 |
-
### Issue: No sound when playing
|
| 240 |
-
**Solution**:
|
| 241 |
-
1. Check browser audio permissions
|
| 242 |
-
2. Ensure audio data loaded (check Network tab)
|
| 243 |
-
3. Try playing in different browser
|
| 244 |
-
4. Check volume settings
|
| 245 |
-
|
| 246 |
-
## π Success Metrics
|
| 247 |
-
|
| 248 |
-
After implementation, users can:
|
| 249 |
-
- β
Generate AI songs with custom lyrics
|
| 250 |
-
- β
Create narrated audio from stories
|
| 251 |
-
- β
Play audio directly in browser
|
| 252 |
-
- β
Manage audio content library
|
| 253 |
-
- β
Use via natural language with Claude
|
| 254 |
-
|
| 255 |
-
## π Key Achievements
|
| 256 |
-
|
| 257 |
-
1. **Full Integration**: Claude β MCP β API β ElevenLabs β UI
|
| 258 |
-
2. **Beautiful UI**: Premium gradient design, smooth animations
|
| 259 |
-
3. **Persistent Storage**: Server-side storage with localStorage fallback
|
| 260 |
-
4. **Real-time Updates**: Auto-refresh keeps content synced
|
| 261 |
-
5. **Error Handling**: Graceful fallbacks and user-friendly messages
|
| 262 |
-
6. **Documentation**: Comprehensive guide for users and developers
|
| 263 |
-
|
| 264 |
-
## π Support
|
| 265 |
-
|
| 266 |
-
For issues or questions:
|
| 267 |
-
1. Check `VOICE_STUDIO_GUIDE.md` for detailed documentation
|
| 268 |
-
2. Review console logs for error messages
|
| 269 |
-
3. Verify ElevenLabs API status
|
| 270 |
-
4. Check Next.js server logs
|
| 271 |
-
|
| 272 |
-
---
|
| 273 |
-
|
| 274 |
-
**Implementation Date**: 2025-11-22
|
| 275 |
-
**Status**: β
Complete and Ready to Use
|
| 276 |
-
**Next Steps**: Set ELEVENLABS_API_KEY and start generating audio!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VOICE_STUDIO_QUICK_START.md
DELETED
|
@@ -1,124 +0,0 @@
|
|
| 1 |
-
# π΅ Voice Studio - Quick Reference
|
| 2 |
-
|
| 3 |
-
## Claude Prompts to Try
|
| 4 |
-
|
| 5 |
-
### πΈ Generate Songs
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
Generate lyrics for a love song in acoustic style and create audio
|
| 9 |
-
```
|
| 10 |
-
|
| 11 |
-
```
|
| 12 |
-
Write a rock anthem about overcoming challenges and generate the music
|
| 13 |
-
```
|
| 14 |
-
|
| 15 |
-
```
|
| 16 |
-
Create a lullaby with soothing lyrics and make it into a song
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
```
|
| 20 |
-
Make a funny birthday song in jazz style with audio
|
| 21 |
-
```
|
| 22 |
-
|
| 23 |
-
### π Generate Stories with Audio
|
| 24 |
-
|
| 25 |
-
```
|
| 26 |
-
Write a 300-word fantasy story about a magical forest and narrate it
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
```
|
| 30 |
-
Tell a spooky ghost story and generate audio narration
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
```
|
| 34 |
-
Create a motivational speech about success and make an audio version
|
| 35 |
-
```
|
| 36 |
-
|
| 37 |
-
```
|
| 38 |
-
Write a children's story about friendship and narrate it
|
| 39 |
-
```
|
| 40 |
-
|
| 41 |
-
### π― Specific Styles
|
| 42 |
-
|
| 43 |
-
**Music Genres:**
|
| 44 |
-
- Pop
|
| 45 |
-
- Rock
|
| 46 |
-
- Jazz
|
| 47 |
-
- Classical
|
| 48 |
-
- EDM
|
| 49 |
-
- Country
|
| 50 |
-
- Folk
|
| 51 |
-
- R&B
|
| 52 |
-
- Hip-Hop
|
| 53 |
-
- Ballad
|
| 54 |
-
- Acoustic
|
| 55 |
-
|
| 56 |
-
**Story Types:**
|
| 57 |
-
- Fantasy
|
| 58 |
-
- Sci-Fi
|
| 59 |
-
- Mystery
|
| 60 |
-
- Romance
|
| 61 |
-
- Adventure
|
| 62 |
-
- Comedy
|
| 63 |
-
- Horror
|
| 64 |
-
- Motivational
|
| 65 |
-
- Educational
|
| 66 |
-
- Bedtime stories
|
| 67 |
-
|
| 68 |
-
## Quick Commands
|
| 69 |
-
|
| 70 |
-
### Open Voice Studio
|
| 71 |
-
- Double-click "Voice Studio" icon on desktop
|
| 72 |
-
- Or say to Claude: "Tell me how to access my generated audio"
|
| 73 |
-
|
| 74 |
-
### Check for New Content
|
| 75 |
-
- Voice Studio auto-refreshes every 5 seconds
|
| 76 |
-
- Or click the "Refresh" button manually
|
| 77 |
-
|
| 78 |
-
### Play Audio
|
| 79 |
-
- Click the Play button (βΆοΈ) on any content card
|
| 80 |
-
- Click again to stop
|
| 81 |
-
|
| 82 |
-
### Delete Content
|
| 83 |
-
- Click the trash icon (ποΈ) next to any content
|
| 84 |
-
|
| 85 |
-
## Tips
|
| 86 |
-
|
| 87 |
-
β
**DO:**
|
| 88 |
-
- Use descriptive titles for easy identification
|
| 89 |
-
- Keep story length under 2000 characters for best quality
|
| 90 |
-
- Specify music style/genre for better results
|
| 91 |
-
- Use your passkey consistently
|
| 92 |
-
|
| 93 |
-
β **DON'T:**
|
| 94 |
-
- Don't make lyrics too long (quality may decrease)
|
| 95 |
-
- Don't close Voice Studio while audio is generating
|
| 96 |
-
- Don't forget to set ELEVENLABS_API_KEY in environment
|
| 97 |
-
|
| 98 |
-
## Example Complete Workflow
|
| 99 |
-
|
| 100 |
-
1. **Open Claude Desktop**
|
| 101 |
-
2. **Say:** "Create a cheerful pop song about summer vacation and generate audio with passkey mykey123"
|
| 102 |
-
3. **Claude generates lyrics and audio**
|
| 103 |
-
4. **Open Voice Studio app** (double-click icon)
|
| 104 |
-
5. **See your song** in the app
|
| 105 |
-
6. **Click Play** and enjoy!
|
| 106 |
-
|
| 107 |
-
## Passkey
|
| 108 |
-
|
| 109 |
-
Your passkey helps keep your generated content organized and secure.
|
| 110 |
-
|
| 111 |
-
**Choose a passkey:**
|
| 112 |
-
- Minimum 4 characters
|
| 113 |
-
- Remember it for future sessions
|
| 114 |
-
- Use same passkey to see all your content
|
| 115 |
-
|
| 116 |
-
**Example passkeys:**
|
| 117 |
-
- `mykey123`
|
| 118 |
-
- `demo2024`
|
| 119 |
-
- `music_lover`
|
| 120 |
-
- `storyteller`
|
| 121 |
-
|
| 122 |
-
---
|
| 123 |
-
|
| 124 |
-
**Need Help?** Check `VOICE_STUDIO_GUIDE.md` for full documentation!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/components/FlutterRunner.tsx
CHANGED
|
@@ -39,8 +39,6 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 39 |
const [activeFileName, setActiveFileName] = useState('main.dart')
|
| 40 |
const [lastSaved, setLastSaved] = useState<Date | null>(null)
|
| 41 |
const [passkey, setPasskey] = useState('')
|
| 42 |
-
const [isUnlocked, setIsUnlocked] = useState(false)
|
| 43 |
-
const [tempPasskey, setTempPasskey] = useState('')
|
| 44 |
const [loading, setLoading] = useState(false)
|
| 45 |
|
| 46 |
// Load files from secure storage
|
|
@@ -112,16 +110,6 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 112 |
}
|
| 113 |
}
|
| 114 |
|
| 115 |
-
const handleUnlock = async () => {
|
| 116 |
-
if (tempPasskey.trim().length >= 4) {
|
| 117 |
-
setPasskey(tempPasskey.trim())
|
| 118 |
-
setIsUnlocked(true)
|
| 119 |
-
await loadFiles(tempPasskey.trim())
|
| 120 |
-
} else {
|
| 121 |
-
alert('Passkey must be at least 4 characters')
|
| 122 |
-
}
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
const handleFileClick = (file: FileNode) => {
|
| 126 |
if (file.type === 'file') {
|
| 127 |
setActiveFileId(file.id)
|
|
@@ -135,45 +123,53 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 135 |
const sessionPasskey = sessionStorage.getItem('currentPasskey')
|
| 136 |
const sessionFileContent = sessionStorage.getItem('flutterFileContent')
|
| 137 |
const sessionFileName = sessionStorage.getItem('currentFileName')
|
| 138 |
-
|
| 139 |
if (sessionFileContent) {
|
| 140 |
// Load file from session storage (from FileManager)
|
| 141 |
console.log('Loading dart file from session:', sessionFileName, 'Content length:', sessionFileContent.length)
|
| 142 |
setCode(sessionFileContent)
|
| 143 |
setActiveFileName(sessionFileName || 'main.dart')
|
| 144 |
-
|
| 145 |
if (sessionPasskey) {
|
| 146 |
setPasskey(sessionPasskey)
|
| 147 |
-
setIsUnlocked(true)
|
| 148 |
loadFiles(sessionPasskey)
|
| 149 |
-
} else {
|
| 150 |
-
// Public file - no passkey required
|
| 151 |
-
setIsUnlocked(true)
|
| 152 |
-
setFiles([{
|
| 153 |
-
id: 'root',
|
| 154 |
-
name: 'lib',
|
| 155 |
-
type: 'folder',
|
| 156 |
-
isOpen: true,
|
| 157 |
-
children: [
|
| 158 |
-
{ id: 'main', name: sessionFileName || 'main.dart', type: 'file', content: sessionFileContent }
|
| 159 |
-
]
|
| 160 |
-
}])
|
| 161 |
}
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
// Clear session storage after loading
|
| 164 |
sessionStorage.removeItem('flutterFileContent')
|
| 165 |
} else if (sessionPasskey) {
|
| 166 |
setPasskey(sessionPasskey)
|
| 167 |
-
setIsUnlocked(true)
|
| 168 |
loadFiles(sessionPasskey)
|
| 169 |
} else if (initialCode) {
|
| 170 |
setCode(initialCode)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
}
|
| 172 |
}, [initialCode])
|
| 173 |
|
| 174 |
-
// Auto-save to file every 2 seconds when code changes
|
| 175 |
useEffect(() => {
|
| 176 |
-
if (!passkey || !
|
| 177 |
|
| 178 |
const saveTimer = setTimeout(async () => {
|
| 179 |
try {
|
|
@@ -196,7 +192,7 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 196 |
}, 2000)
|
| 197 |
|
| 198 |
return () => clearTimeout(saveTimer)
|
| 199 |
-
}, [code, passkey, activeFileName
|
| 200 |
|
| 201 |
const handleDownload = () => {
|
| 202 |
const blob = new Blob([code], { type: 'text/plain' })
|
|
@@ -225,163 +221,138 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 225 |
y={40}
|
| 226 |
className="flutter-ide-window"
|
| 227 |
>
|
| 228 |
-
{!isUnlocked ? (
|
| 229 |
-
<div className="flex h-full bg-[#1e1e1e] items-center justify-center">
|
| 230 |
-
<div className="bg-[#252526] p-8 rounded-lg shadow-xl border border-[#333] max-w-md w-full">
|
| 231 |
-
<h2 className="text-xl font-bold text-white mb-4">Enter Passkey</h2>
|
| 232 |
-
<p className="text-gray-400 mb-6">Enter your passkey to access Dart/Flutter files</p>
|
| 233 |
-
<input
|
| 234 |
-
type="password"
|
| 235 |
-
value={tempPasskey}
|
| 236 |
-
onChange={(e) => setTempPasskey(e.target.value)}
|
| 237 |
-
onKeyPress={(e) => e.key === 'Enter' && handleUnlock()}
|
| 238 |
-
placeholder="Your passkey"
|
| 239 |
-
className="w-full px-4 py-2 bg-[#1e1e1e] border border-[#333] rounded text-white focus:outline-none focus:border-blue-500"
|
| 240 |
-
autoFocus
|
| 241 |
-
/>
|
| 242 |
-
<button
|
| 243 |
-
onClick={handleUnlock}
|
| 244 |
-
disabled={loading}
|
| 245 |
-
className="w-full mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium disabled:opacity-50"
|
| 246 |
-
>
|
| 247 |
-
{loading ? 'Loading...' : 'Unlock'}
|
| 248 |
-
</button>
|
| 249 |
-
</div>
|
| 250 |
-
</div>
|
| 251 |
-
) : (
|
| 252 |
<div className="flex h-full bg-[#1e1e1e] text-gray-300 font-sans">
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
</div>
|
| 271 |
-
{lastSaved && (
|
| 272 |
-
<span className="text-xs text-green-400">
|
| 273 |
-
β Saved {lastSaved.toLocaleTimeString()}
|
| 274 |
-
</span>
|
| 275 |
-
)}
|
| 276 |
</div>
|
|
|
|
| 277 |
|
| 278 |
-
|
|
|
|
|
|
|
| 279 |
<button
|
| 280 |
-
onClick={
|
| 281 |
-
className=
|
| 282 |
-
|
|
|
|
|
|
|
| 283 |
>
|
| 284 |
-
<
|
|
|
|
|
|
|
|
|
|
| 285 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
</div>
|
| 287 |
-
|
|
|
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
value={code}
|
| 296 |
-
onChange={(value) => setCode(value || '')}
|
| 297 |
-
options={{
|
| 298 |
-
minimap: { enabled: false },
|
| 299 |
-
fontSize: 14,
|
| 300 |
-
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
| 301 |
-
lineNumbers: 'on',
|
| 302 |
-
scrollBeyondLastLine: false,
|
| 303 |
-
automaticLayout: true,
|
| 304 |
-
padding: { top: 16, bottom: 16 },
|
| 305 |
-
renderLineHighlight: 'all',
|
| 306 |
-
smoothScrolling: true,
|
| 307 |
-
cursorBlinking: 'smooth',
|
| 308 |
-
cursorSmoothCaretAnimation: 'on'
|
| 309 |
-
}}
|
| 310 |
-
/>
|
| 311 |
-
</div>
|
| 312 |
-
</div>
|
| 313 |
-
)}
|
| 314 |
-
|
| 315 |
-
{/* DartPad Preview - Takes full width when editor is hidden */}
|
| 316 |
-
<div className="flex-1 flex flex-col">
|
| 317 |
-
<div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
|
| 318 |
-
<button
|
| 319 |
-
onClick={() => setShowEditor(!showEditor)}
|
| 320 |
-
className={`flex items-center gap-2 px-2 py-1 rounded transition-all ${showEditor
|
| 321 |
-
? 'bg-[#3e3e42] text-white hover:bg-[#4e4e52]'
|
| 322 |
-
: 'bg-blue-600 text-white hover:bg-blue-700'}`}
|
| 323 |
-
title={showEditor ? "Hide Code Editor" : "Show Code Editor"}
|
| 324 |
-
>
|
| 325 |
-
<SidebarSimple size={16} weight={showEditor ? "fill" : "regular"} />
|
| 326 |
-
<span className="text-xs font-medium">
|
| 327 |
-
{showEditor ? "Hide Editor" : "Show Editor"}
|
| 328 |
-
</span>
|
| 329 |
-
</button>
|
| 330 |
-
<span className="text-xs text-gray-400 uppercase font-bold">
|
| 331 |
-
Live Preview {!showEditor && "- Full Screen"}
|
| 332 |
-
</span>
|
| 333 |
-
<div className="w-24" /> {/* Spacer for centering */}
|
| 334 |
-
</div>
|
| 335 |
-
<div className="flex-1 bg-[#1e1e1e] relative overflow-hidden">
|
| 336 |
-
<iframe
|
| 337 |
-
|
| 338 |
-
src="https://dartpad.dev/embed-flutter.html?theme=dark&split=50"
|
| 339 |
-
className="w-full h-full border-0"
|
| 340 |
-
sandbox="allow-scripts allow-same-origin allow-popups"
|
| 341 |
-
title="Flutter Preview"
|
| 342 |
-
/>
|
| 343 |
</div>
|
| 344 |
-
</div>
|
| 345 |
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
</div>
|
| 365 |
-
{file.children?.map(child => (
|
| 366 |
-
<div
|
| 367 |
-
key={child.id}
|
| 368 |
-
onClick={() => handleFileClick(child)}
|
| 369 |
-
className={`flex items-center gap-2 py-1 px-2 ml-4 text-sm rounded cursor-pointer ${activeFileId === child.id ? 'bg-[#37373d] text-white' : 'text-gray-400 hover:bg-[#2a2d2e]'
|
| 370 |
-
}`}
|
| 371 |
-
>
|
| 372 |
-
<FileCode size={14} />
|
| 373 |
-
{child.name}
|
| 374 |
</div>
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
</div>
|
| 379 |
</div>
|
| 380 |
</div>
|
| 381 |
-
|
| 382 |
-
)}
|
| 383 |
</div>
|
| 384 |
-
)}
|
| 385 |
</Window>
|
| 386 |
)
|
| 387 |
}
|
|
|
|
| 39 |
const [activeFileName, setActiveFileName] = useState('main.dart')
|
| 40 |
const [lastSaved, setLastSaved] = useState<Date | null>(null)
|
| 41 |
const [passkey, setPasskey] = useState('')
|
|
|
|
|
|
|
| 42 |
const [loading, setLoading] = useState(false)
|
| 43 |
|
| 44 |
// Load files from secure storage
|
|
|
|
| 110 |
}
|
| 111 |
}
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
const handleFileClick = (file: FileNode) => {
|
| 114 |
if (file.type === 'file') {
|
| 115 |
setActiveFileId(file.id)
|
|
|
|
| 123 |
const sessionPasskey = sessionStorage.getItem('currentPasskey')
|
| 124 |
const sessionFileContent = sessionStorage.getItem('flutterFileContent')
|
| 125 |
const sessionFileName = sessionStorage.getItem('currentFileName')
|
| 126 |
+
|
| 127 |
if (sessionFileContent) {
|
| 128 |
// Load file from session storage (from FileManager)
|
| 129 |
console.log('Loading dart file from session:', sessionFileName, 'Content length:', sessionFileContent.length)
|
| 130 |
setCode(sessionFileContent)
|
| 131 |
setActiveFileName(sessionFileName || 'main.dart')
|
| 132 |
+
|
| 133 |
if (sessionPasskey) {
|
| 134 |
setPasskey(sessionPasskey)
|
|
|
|
| 135 |
loadFiles(sessionPasskey)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
}
|
| 137 |
+
|
| 138 |
+
// Set up file structure
|
| 139 |
+
setFiles([{
|
| 140 |
+
id: 'root',
|
| 141 |
+
name: 'lib',
|
| 142 |
+
type: 'folder',
|
| 143 |
+
isOpen: true,
|
| 144 |
+
children: [
|
| 145 |
+
{ id: 'main', name: sessionFileName || 'main.dart', type: 'file', content: sessionFileContent }
|
| 146 |
+
]
|
| 147 |
+
}])
|
| 148 |
+
|
| 149 |
// Clear session storage after loading
|
| 150 |
sessionStorage.removeItem('flutterFileContent')
|
| 151 |
} else if (sessionPasskey) {
|
| 152 |
setPasskey(sessionPasskey)
|
|
|
|
| 153 |
loadFiles(sessionPasskey)
|
| 154 |
} else if (initialCode) {
|
| 155 |
setCode(initialCode)
|
| 156 |
+
} else {
|
| 157 |
+
// Initialize with default empty file
|
| 158 |
+
setFiles([{
|
| 159 |
+
id: 'root',
|
| 160 |
+
name: 'lib',
|
| 161 |
+
type: 'folder',
|
| 162 |
+
isOpen: true,
|
| 163 |
+
children: [
|
| 164 |
+
{ id: 'main', name: 'main.dart', type: 'file', content: DEFAULT_FLUTTER_CODE }
|
| 165 |
+
]
|
| 166 |
+
}])
|
| 167 |
}
|
| 168 |
}, [initialCode])
|
| 169 |
|
| 170 |
+
// Auto-save to file every 2 seconds when code changes (only if passkey is set)
|
| 171 |
useEffect(() => {
|
| 172 |
+
if (!passkey || !activeFileName) return
|
| 173 |
|
| 174 |
const saveTimer = setTimeout(async () => {
|
| 175 |
try {
|
|
|
|
| 192 |
}, 2000)
|
| 193 |
|
| 194 |
return () => clearTimeout(saveTimer)
|
| 195 |
+
}, [code, passkey, activeFileName])
|
| 196 |
|
| 197 |
const handleDownload = () => {
|
| 198 |
const blob = new Blob([code], { type: 'text/plain' })
|
|
|
|
| 221 |
y={40}
|
| 222 |
className="flutter-ide-window"
|
| 223 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
<div className="flex h-full bg-[#1e1e1e] text-gray-300 font-sans">
|
| 225 |
+
{/* Code Editor - Conditionally Rendered */}
|
| 226 |
+
{showEditor && (
|
| 227 |
+
<div className="w-[600px] flex flex-col border-r border-[#333]">
|
| 228 |
+
{/* Toolbar */}
|
| 229 |
+
<div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
|
| 230 |
+
<div className="flex items-center gap-2">
|
| 231 |
+
<button
|
| 232 |
+
onClick={() => setShowFiles(!showFiles)}
|
| 233 |
+
className={`p-1.5 rounded hover:bg-[#3e3e42] ${showFiles ? 'text-white' : 'text-gray-500'}`}
|
| 234 |
+
title="Toggle Files"
|
| 235 |
+
>
|
| 236 |
+
<SidebarSimple size={16} />
|
| 237 |
+
</button>
|
| 238 |
+
<div className="h-4 w-[1px] bg-gray-600 mx-2" />
|
| 239 |
+
<div className="flex items-center gap-2 px-3 py-1 bg-[#1e1e1e] rounded text-xs text-gray-400 border border-[#333]">
|
| 240 |
+
<FileCode size={14} className="text-blue-400" />
|
| 241 |
+
{activeFileName}
|
| 242 |
+
</div>
|
| 243 |
+
{lastSaved && (
|
| 244 |
+
<span className="text-xs text-green-400">
|
| 245 |
+
β Saved {lastSaved.toLocaleTimeString()}
|
| 246 |
+
</span>
|
| 247 |
+
)}
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<div className="flex items-center gap-2">
|
| 251 |
+
<button
|
| 252 |
+
onClick={handleDownload}
|
| 253 |
+
className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
|
| 254 |
+
title="Download Code"
|
| 255 |
+
>
|
| 256 |
+
<Download size={16} />
|
| 257 |
+
</button>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
{/* Monaco Editor */}
|
| 262 |
+
<div className="flex-1">
|
| 263 |
+
<Editor
|
| 264 |
+
height="100%"
|
| 265 |
+
defaultLanguage="dart"
|
| 266 |
+
theme="vs-dark"
|
| 267 |
+
value={code}
|
| 268 |
+
onChange={(value) => setCode(value || '')}
|
| 269 |
+
options={{
|
| 270 |
+
minimap: { enabled: false },
|
| 271 |
+
fontSize: 14,
|
| 272 |
+
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
| 273 |
+
lineNumbers: 'on',
|
| 274 |
+
scrollBeyondLastLine: false,
|
| 275 |
+
automaticLayout: true,
|
| 276 |
+
padding: { top: 16, bottom: 16 },
|
| 277 |
+
renderLineHighlight: 'all',
|
| 278 |
+
smoothScrolling: true,
|
| 279 |
+
cursorBlinking: 'smooth',
|
| 280 |
+
cursorSmoothCaretAnimation: 'on'
|
| 281 |
+
}}
|
| 282 |
+
/>
|
| 283 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
</div>
|
| 285 |
+
)}
|
| 286 |
|
| 287 |
+
{/* DartPad Preview - Takes full width when editor is hidden */}
|
| 288 |
+
<div className="flex-1 flex flex-col">
|
| 289 |
+
<div className="h-10 bg-[#2d2d2d] border-b border-[#1e1e1e] flex items-center justify-between px-4">
|
| 290 |
<button
|
| 291 |
+
onClick={() => setShowEditor(!showEditor)}
|
| 292 |
+
className={`flex items-center gap-2 px-2 py-1 rounded transition-all ${showEditor
|
| 293 |
+
? 'bg-[#3e3e42] text-white hover:bg-[#4e4e52]'
|
| 294 |
+
: 'bg-blue-600 text-white hover:bg-blue-700'}`}
|
| 295 |
+
title={showEditor ? "Hide Code Editor" : "Show Code Editor"}
|
| 296 |
>
|
| 297 |
+
<SidebarSimple size={16} weight={showEditor ? "fill" : "regular"} />
|
| 298 |
+
<span className="text-xs font-medium">
|
| 299 |
+
{showEditor ? "Hide Editor" : "Show Editor"}
|
| 300 |
+
</span>
|
| 301 |
</button>
|
| 302 |
+
<span className="text-xs text-gray-400 uppercase font-bold">
|
| 303 |
+
Live Preview {!showEditor && "- Full Screen"}
|
| 304 |
+
</span>
|
| 305 |
+
<div className="w-24" /> {/* Spacer for centering */}
|
| 306 |
</div>
|
| 307 |
+
<div className="flex-1 bg-[#1e1e1e] relative overflow-hidden">
|
| 308 |
+
<iframe
|
| 309 |
|
| 310 |
+
src="https://dartpad.dev/embed-flutter.html?theme=dark&split=50"
|
| 311 |
+
className="w-full h-full border-0"
|
| 312 |
+
sandbox="allow-scripts allow-same-origin allow-popups"
|
| 313 |
+
title="Flutter Preview"
|
| 314 |
+
/>
|
| 315 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
</div>
|
|
|
|
| 317 |
|
| 318 |
+
{/* File List - Right Side - Only show when editor is visible */}
|
| 319 |
+
{showEditor && showFiles && (
|
| 320 |
+
<div className="w-64 bg-[#252526] border-l border-[#333] flex flex-col">
|
| 321 |
+
<div className="h-9 px-4 flex items-center text-xs font-bold text-gray-500 uppercase tracking-wider">
|
| 322 |
+
Project Files
|
| 323 |
+
</div>
|
| 324 |
+
<div className="flex-1 overflow-y-auto py-2">
|
| 325 |
+
<div className="px-2">
|
| 326 |
+
<div className="flex items-center gap-1 py-1 px-2 text-sm text-gray-300 hover:bg-[#2a2d2e] rounded cursor-pointer">
|
| 327 |
+
<CaretDown size={12} weight="bold" />
|
| 328 |
+
<span className="font-bold">Flutter App</span>
|
| 329 |
+
</div>
|
| 330 |
+
<div className="pl-4">
|
| 331 |
+
{files.map(file => (
|
| 332 |
+
<div key={file.id}>
|
| 333 |
+
<div className="flex items-center gap-2 py-1 px-2 text-sm hover:bg-[#2a2d2e] rounded cursor-pointer text-blue-400">
|
| 334 |
+
<CaretDown size={12} weight="bold" />
|
| 335 |
+
{file.name}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
</div>
|
| 337 |
+
{file.children?.map(child => (
|
| 338 |
+
<div
|
| 339 |
+
key={child.id}
|
| 340 |
+
onClick={() => handleFileClick(child)}
|
| 341 |
+
className={`flex items-center gap-2 py-1 px-2 ml-4 text-sm rounded cursor-pointer ${activeFileId === child.id ? 'bg-[#37373d] text-white' : 'text-gray-400 hover:bg-[#2a2d2e]'
|
| 342 |
+
}`}
|
| 343 |
+
>
|
| 344 |
+
<FileCode size={14} />
|
| 345 |
+
{child.name}
|
| 346 |
+
</div>
|
| 347 |
+
))}
|
| 348 |
+
</div>
|
| 349 |
+
))}
|
| 350 |
+
</div>
|
| 351 |
</div>
|
| 352 |
</div>
|
| 353 |
</div>
|
| 354 |
+
)}
|
|
|
|
| 355 |
</div>
|
|
|
|
| 356 |
</Window>
|
| 357 |
)
|
| 358 |
}
|
frontend-fetch-example.js
DELETED
|
@@ -1,462 +0,0 @@
|
|
| 1 |
-
// frontend-fetch-example.js
|
| 2 |
-
// Frontend code snippet for Reuben OS to fetch and display session files
|
| 3 |
-
|
| 4 |
-
// React Component Example for Session Management
|
| 5 |
-
import React, { useState, useEffect, useCallback } from 'react';
|
| 6 |
-
|
| 7 |
-
// Configuration
|
| 8 |
-
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
| 9 |
-
const POLL_INTERVAL = 5000; // Poll every 5 seconds
|
| 10 |
-
|
| 11 |
-
// ===== SESSION MANAGER COMPONENT =====
|
| 12 |
-
export function SessionManager() {
|
| 13 |
-
const [sessionId, setSessionId] = useState('');
|
| 14 |
-
const [files, setFiles] = useState([]);
|
| 15 |
-
const [loading, setLoading] = useState(false);
|
| 16 |
-
const [error, setError] = useState(null);
|
| 17 |
-
const [quizDetected, setQuizDetected] = useState(false);
|
| 18 |
-
|
| 19 |
-
// Generate a new session ID on component mount
|
| 20 |
-
useEffect(() => {
|
| 21 |
-
const generateSessionId = () => {
|
| 22 |
-
const timestamp = Date.now();
|
| 23 |
-
const random = Math.random().toString(36).substring(2, 9);
|
| 24 |
-
return `session_${timestamp}_${random}`;
|
| 25 |
-
};
|
| 26 |
-
|
| 27 |
-
// Check if session ID exists in localStorage, otherwise generate new one
|
| 28 |
-
const storedSessionId = localStorage.getItem('reubenOSSessionId');
|
| 29 |
-
if (storedSessionId) {
|
| 30 |
-
setSessionId(storedSessionId);
|
| 31 |
-
} else {
|
| 32 |
-
const newSessionId = generateSessionId();
|
| 33 |
-
setSessionId(newSessionId);
|
| 34 |
-
localStorage.setItem('reubenOSSessionId', newSessionId);
|
| 35 |
-
}
|
| 36 |
-
}, []);
|
| 37 |
-
|
| 38 |
-
// Fetch files for the current session
|
| 39 |
-
const fetchFiles = useCallback(async () => {
|
| 40 |
-
if (!sessionId) return;
|
| 41 |
-
|
| 42 |
-
setLoading(true);
|
| 43 |
-
setError(null);
|
| 44 |
-
|
| 45 |
-
try {
|
| 46 |
-
const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`);
|
| 47 |
-
|
| 48 |
-
if (!response.ok) {
|
| 49 |
-
throw new Error(`HTTP error! status: ${response.status}`);
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
const data = await response.json();
|
| 53 |
-
|
| 54 |
-
if (data.success) {
|
| 55 |
-
setFiles(data.files || []);
|
| 56 |
-
|
| 57 |
-
// Check if quiz.json exists
|
| 58 |
-
const hasQuiz = data.files.some(file => file.name === 'quiz.json');
|
| 59 |
-
setQuizDetected(hasQuiz);
|
| 60 |
-
} else {
|
| 61 |
-
setError(data.error || 'Failed to fetch files');
|
| 62 |
-
}
|
| 63 |
-
} catch (err) {
|
| 64 |
-
console.error('Error fetching files:', err);
|
| 65 |
-
setError(err.message);
|
| 66 |
-
} finally {
|
| 67 |
-
setLoading(false);
|
| 68 |
-
}
|
| 69 |
-
}, [sessionId]);
|
| 70 |
-
|
| 71 |
-
// Set up polling
|
| 72 |
-
useEffect(() => {
|
| 73 |
-
if (!sessionId) return;
|
| 74 |
-
|
| 75 |
-
// Initial fetch
|
| 76 |
-
fetchFiles();
|
| 77 |
-
|
| 78 |
-
// Set up polling interval
|
| 79 |
-
const interval = setInterval(fetchFiles, POLL_INTERVAL);
|
| 80 |
-
|
| 81 |
-
return () => clearInterval(interval);
|
| 82 |
-
}, [sessionId, fetchFiles]);
|
| 83 |
-
|
| 84 |
-
// Handle manual refresh
|
| 85 |
-
const handleRefresh = () => {
|
| 86 |
-
fetchFiles();
|
| 87 |
-
};
|
| 88 |
-
|
| 89 |
-
// Copy session ID to clipboard
|
| 90 |
-
const copySessionId = () => {
|
| 91 |
-
navigator.clipboard.writeText(sessionId);
|
| 92 |
-
alert('Session ID copied to clipboard!');
|
| 93 |
-
};
|
| 94 |
-
|
| 95 |
-
return (
|
| 96 |
-
<div className="session-manager">
|
| 97 |
-
<div className="session-header">
|
| 98 |
-
<h2>Session Manager</h2>
|
| 99 |
-
<div className="session-info">
|
| 100 |
-
<span>Session ID: {sessionId}</span>
|
| 101 |
-
<button onClick={copySessionId}>Copy ID</button>
|
| 102 |
-
</div>
|
| 103 |
-
<button onClick={handleRefresh} disabled={loading}>
|
| 104 |
-
{loading ? 'Loading...' : 'Refresh'}
|
| 105 |
-
</button>
|
| 106 |
-
</div>
|
| 107 |
-
|
| 108 |
-
{error && (
|
| 109 |
-
<div className="error-message">
|
| 110 |
-
Error: {error}
|
| 111 |
-
</div>
|
| 112 |
-
)}
|
| 113 |
-
|
| 114 |
-
{quizDetected && (
|
| 115 |
-
<div className="quiz-alert">
|
| 116 |
-
π― Quiz Detected! Click to launch Quiz App
|
| 117 |
-
<button onClick={() => launchQuizApp(sessionId)}>
|
| 118 |
-
Launch Quiz
|
| 119 |
-
</button>
|
| 120 |
-
</div>
|
| 121 |
-
)}
|
| 122 |
-
|
| 123 |
-
<div className="files-list">
|
| 124 |
-
<h3>Files ({files.length})</h3>
|
| 125 |
-
{files.length === 0 ? (
|
| 126 |
-
<p>No files yet. Use Claude to save files to this session.</p>
|
| 127 |
-
) : (
|
| 128 |
-
<ul>
|
| 129 |
-
{files.map((file, index) => (
|
| 130 |
-
<FileItem key={index} file={file} sessionId={sessionId} />
|
| 131 |
-
))}
|
| 132 |
-
</ul>
|
| 133 |
-
)}
|
| 134 |
-
</div>
|
| 135 |
-
</div>
|
| 136 |
-
);
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
// ===== FILE ITEM COMPONENT =====
|
| 140 |
-
function FileItem({ file, sessionId }) {
|
| 141 |
-
const [showContent, setShowContent] = useState(false);
|
| 142 |
-
|
| 143 |
-
const handleDownload = () => {
|
| 144 |
-
// Create a download link
|
| 145 |
-
const blob = new Blob([file.content || ''], { type: 'text/plain' });
|
| 146 |
-
const url = URL.createObjectURL(blob);
|
| 147 |
-
const a = document.createElement('a');
|
| 148 |
-
a.href = url;
|
| 149 |
-
a.download = file.name;
|
| 150 |
-
a.click();
|
| 151 |
-
URL.revokeObjectURL(url);
|
| 152 |
-
};
|
| 153 |
-
|
| 154 |
-
const getFileIcon = (fileName) => {
|
| 155 |
-
if (fileName.endsWith('.dart')) return 'π―';
|
| 156 |
-
if (fileName.endsWith('.tex')) return 'π';
|
| 157 |
-
if (fileName.endsWith('.json')) return 'π';
|
| 158 |
-
if (fileName.endsWith('.js') || fileName.endsWith('.ts')) return 'π';
|
| 159 |
-
if (fileName.endsWith('.py')) return 'π';
|
| 160 |
-
return 'π';
|
| 161 |
-
};
|
| 162 |
-
|
| 163 |
-
return (
|
| 164 |
-
<li className="file-item">
|
| 165 |
-
<div className="file-header">
|
| 166 |
-
<span className="file-icon">{getFileIcon(file.name)}</span>
|
| 167 |
-
<span className="file-name">{file.name}</span>
|
| 168 |
-
<span className="file-size">({(file.size / 1024).toFixed(2)} KB)</span>
|
| 169 |
-
<button onClick={() => setShowContent(!showContent)}>
|
| 170 |
-
{showContent ? 'Hide' : 'Show'}
|
| 171 |
-
</button>
|
| 172 |
-
<button onClick={handleDownload}>Download</button>
|
| 173 |
-
</div>
|
| 174 |
-
|
| 175 |
-
{showContent && file.content && (
|
| 176 |
-
<div className="file-content">
|
| 177 |
-
<pre>{file.content.substring(0, 500)}</pre>
|
| 178 |
-
{file.content.length > 500 && <p>... (truncated)</p>}
|
| 179 |
-
</div>
|
| 180 |
-
)}
|
| 181 |
-
</li>
|
| 182 |
-
);
|
| 183 |
-
}
|
| 184 |
-
|
| 185 |
-
// ===== QUIZ APP LAUNCHER =====
|
| 186 |
-
function launchQuizApp(sessionId) {
|
| 187 |
-
// This function would navigate to your Quiz app with the session ID
|
| 188 |
-
window.location.href = `/apps/quiz?sessionId=${sessionId}`;
|
| 189 |
-
}
|
| 190 |
-
|
| 191 |
-
// ===== UTILITY FUNCTIONS =====
|
| 192 |
-
|
| 193 |
-
// Function to save a file from the frontend (for testing)
|
| 194 |
-
export async function saveFile(sessionId, fileName, content) {
|
| 195 |
-
try {
|
| 196 |
-
const response = await fetch(`${API_URL}/api/mcp-handler`, {
|
| 197 |
-
method: 'POST',
|
| 198 |
-
headers: {
|
| 199 |
-
'Content-Type': 'application/json',
|
| 200 |
-
},
|
| 201 |
-
body: JSON.stringify({
|
| 202 |
-
sessionId,
|
| 203 |
-
action: 'save_file',
|
| 204 |
-
fileName,
|
| 205 |
-
content,
|
| 206 |
-
}),
|
| 207 |
-
});
|
| 208 |
-
|
| 209 |
-
const data = await response.json();
|
| 210 |
-
return data;
|
| 211 |
-
} catch (error) {
|
| 212 |
-
console.error('Error saving file:', error);
|
| 213 |
-
throw error;
|
| 214 |
-
}
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
// Function to clear all files for a session
|
| 218 |
-
export async function clearSession(sessionId) {
|
| 219 |
-
try {
|
| 220 |
-
const response = await fetch(`${API_URL}/api/mcp-handler`, {
|
| 221 |
-
method: 'POST',
|
| 222 |
-
headers: {
|
| 223 |
-
'Content-Type': 'application/json',
|
| 224 |
-
},
|
| 225 |
-
body: JSON.stringify({
|
| 226 |
-
sessionId,
|
| 227 |
-
action: 'clear_session',
|
| 228 |
-
fileName: '',
|
| 229 |
-
content: '',
|
| 230 |
-
}),
|
| 231 |
-
});
|
| 232 |
-
|
| 233 |
-
const data = await response.json();
|
| 234 |
-
return data;
|
| 235 |
-
} catch (error) {
|
| 236 |
-
console.error('Error clearing session:', error);
|
| 237 |
-
throw error;
|
| 238 |
-
}
|
| 239 |
-
}
|
| 240 |
-
|
| 241 |
-
// ===== FLUTTER APP COMPONENT =====
|
| 242 |
-
export function FlutterAppViewer({ sessionId }) {
|
| 243 |
-
const [dartFiles, setDartFiles] = useState([]);
|
| 244 |
-
|
| 245 |
-
useEffect(() => {
|
| 246 |
-
const fetchDartFiles = async () => {
|
| 247 |
-
try {
|
| 248 |
-
const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`);
|
| 249 |
-
const data = await response.json();
|
| 250 |
-
|
| 251 |
-
if (data.success) {
|
| 252 |
-
// Filter only .dart files
|
| 253 |
-
const dartOnly = data.files.filter(f => f.name.endsWith('.dart'));
|
| 254 |
-
setDartFiles(dartOnly);
|
| 255 |
-
}
|
| 256 |
-
} catch (error) {
|
| 257 |
-
console.error('Error fetching Dart files:', error);
|
| 258 |
-
}
|
| 259 |
-
};
|
| 260 |
-
|
| 261 |
-
if (sessionId) {
|
| 262 |
-
fetchDartFiles();
|
| 263 |
-
const interval = setInterval(fetchDartFiles, POLL_INTERVAL);
|
| 264 |
-
return () => clearInterval(interval);
|
| 265 |
-
}
|
| 266 |
-
}, [sessionId]);
|
| 267 |
-
|
| 268 |
-
const openInZapp = (file) => {
|
| 269 |
-
// Open Dart code in Zapp runner
|
| 270 |
-
const zappUrl = 'https://zapp.run/';
|
| 271 |
-
const code = encodeURIComponent(file.content);
|
| 272 |
-
window.open(`${zappUrl}?code=${code}`, '_blank');
|
| 273 |
-
};
|
| 274 |
-
|
| 275 |
-
return (
|
| 276 |
-
<div className="flutter-viewer">
|
| 277 |
-
<h3>Flutter/Dart Files</h3>
|
| 278 |
-
{dartFiles.map((file, index) => (
|
| 279 |
-
<div key={index} className="dart-file">
|
| 280 |
-
<span>{file.name}</span>
|
| 281 |
-
<button onClick={() => openInZapp(file)}>
|
| 282 |
-
Open in Zapp
|
| 283 |
-
</button>
|
| 284 |
-
</div>
|
| 285 |
-
))}
|
| 286 |
-
</div>
|
| 287 |
-
);
|
| 288 |
-
}
|
| 289 |
-
|
| 290 |
-
// ===== LATEX VIEWER COMPONENT =====
|
| 291 |
-
export function LaTeXViewer({ sessionId }) {
|
| 292 |
-
const [texFiles, setTexFiles] = useState([]);
|
| 293 |
-
|
| 294 |
-
useEffect(() => {
|
| 295 |
-
const fetchTexFiles = async () => {
|
| 296 |
-
try {
|
| 297 |
-
const response = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`);
|
| 298 |
-
const data = await response.json();
|
| 299 |
-
|
| 300 |
-
if (data.success) {
|
| 301 |
-
// Filter only .tex files
|
| 302 |
-
const texOnly = data.files.filter(f => f.name.endsWith('.tex'));
|
| 303 |
-
setTexFiles(texOnly);
|
| 304 |
-
}
|
| 305 |
-
} catch (error) {
|
| 306 |
-
console.error('Error fetching LaTeX files:', error);
|
| 307 |
-
}
|
| 308 |
-
};
|
| 309 |
-
|
| 310 |
-
if (sessionId) {
|
| 311 |
-
fetchTexFiles();
|
| 312 |
-
const interval = setInterval(fetchTexFiles, POLL_INTERVAL);
|
| 313 |
-
return () => clearInterval(interval);
|
| 314 |
-
}
|
| 315 |
-
}, [sessionId]);
|
| 316 |
-
|
| 317 |
-
return (
|
| 318 |
-
<div className="latex-viewer">
|
| 319 |
-
<h3>LaTeX Documents</h3>
|
| 320 |
-
{texFiles.map((file, index) => (
|
| 321 |
-
<div key={index} className="tex-file">
|
| 322 |
-
<span>{file.name}</span>
|
| 323 |
-
<button onClick={() => openInLatexEditor(file)}>
|
| 324 |
-
Open in Editor
|
| 325 |
-
</button>
|
| 326 |
-
</div>
|
| 327 |
-
))}
|
| 328 |
-
</div>
|
| 329 |
-
);
|
| 330 |
-
}
|
| 331 |
-
|
| 332 |
-
function openInLatexEditor(file) {
|
| 333 |
-
// Your LaTeX editor integration
|
| 334 |
-
window.location.href = `/apps/latex-studio?file=${file.name}`;
|
| 335 |
-
}
|
| 336 |
-
|
| 337 |
-
// ===== EXAMPLE USAGE IN NEXT.JS PAGE =====
|
| 338 |
-
/*
|
| 339 |
-
// pages/index.js or pages/dashboard.js
|
| 340 |
-
|
| 341 |
-
import { SessionManager } from '../components/SessionManager';
|
| 342 |
-
|
| 343 |
-
export default function Dashboard() {
|
| 344 |
-
return (
|
| 345 |
-
<div className="dashboard">
|
| 346 |
-
<h1>Reuben OS - File Manager</h1>
|
| 347 |
-
<SessionManager />
|
| 348 |
-
</div>
|
| 349 |
-
);
|
| 350 |
-
}
|
| 351 |
-
*/
|
| 352 |
-
|
| 353 |
-
// ===== CSS STYLES (add to your global styles or styled-components) =====
|
| 354 |
-
const styles = `
|
| 355 |
-
.session-manager {
|
| 356 |
-
max-width: 800px;
|
| 357 |
-
margin: 0 auto;
|
| 358 |
-
padding: 20px;
|
| 359 |
-
}
|
| 360 |
-
|
| 361 |
-
.session-header {
|
| 362 |
-
background: #f5f5f5;
|
| 363 |
-
padding: 15px;
|
| 364 |
-
border-radius: 8px;
|
| 365 |
-
margin-bottom: 20px;
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
-
.session-info {
|
| 369 |
-
display: flex;
|
| 370 |
-
align-items: center;
|
| 371 |
-
gap: 10px;
|
| 372 |
-
margin: 10px 0;
|
| 373 |
-
font-family: monospace;
|
| 374 |
-
background: white;
|
| 375 |
-
padding: 10px;
|
| 376 |
-
border-radius: 4px;
|
| 377 |
-
}
|
| 378 |
-
|
| 379 |
-
.error-message {
|
| 380 |
-
background: #fee;
|
| 381 |
-
color: #c00;
|
| 382 |
-
padding: 10px;
|
| 383 |
-
border-radius: 4px;
|
| 384 |
-
margin: 10px 0;
|
| 385 |
-
}
|
| 386 |
-
|
| 387 |
-
.quiz-alert {
|
| 388 |
-
background: #efe;
|
| 389 |
-
color: #060;
|
| 390 |
-
padding: 15px;
|
| 391 |
-
border-radius: 4px;
|
| 392 |
-
margin: 10px 0;
|
| 393 |
-
display: flex;
|
| 394 |
-
justify-content: space-between;
|
| 395 |
-
align-items: center;
|
| 396 |
-
}
|
| 397 |
-
|
| 398 |
-
.files-list {
|
| 399 |
-
background: white;
|
| 400 |
-
padding: 15px;
|
| 401 |
-
border-radius: 8px;
|
| 402 |
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 403 |
-
}
|
| 404 |
-
|
| 405 |
-
.file-item {
|
| 406 |
-
list-style: none;
|
| 407 |
-
padding: 10px;
|
| 408 |
-
border-bottom: 1px solid #eee;
|
| 409 |
-
}
|
| 410 |
-
|
| 411 |
-
.file-header {
|
| 412 |
-
display: flex;
|
| 413 |
-
align-items: center;
|
| 414 |
-
gap: 10px;
|
| 415 |
-
}
|
| 416 |
-
|
| 417 |
-
.file-icon {
|
| 418 |
-
font-size: 20px;
|
| 419 |
-
}
|
| 420 |
-
|
| 421 |
-
.file-name {
|
| 422 |
-
flex: 1;
|
| 423 |
-
font-weight: 500;
|
| 424 |
-
}
|
| 425 |
-
|
| 426 |
-
.file-size {
|
| 427 |
-
color: #666;
|
| 428 |
-
font-size: 0.9em;
|
| 429 |
-
}
|
| 430 |
-
|
| 431 |
-
.file-content {
|
| 432 |
-
margin-top: 10px;
|
| 433 |
-
padding: 10px;
|
| 434 |
-
background: #f5f5f5;
|
| 435 |
-
border-radius: 4px;
|
| 436 |
-
overflow-x: auto;
|
| 437 |
-
}
|
| 438 |
-
|
| 439 |
-
.file-content pre {
|
| 440 |
-
margin: 0;
|
| 441 |
-
font-size: 0.9em;
|
| 442 |
-
line-height: 1.4;
|
| 443 |
-
}
|
| 444 |
-
|
| 445 |
-
button {
|
| 446 |
-
padding: 6px 12px;
|
| 447 |
-
background: #007bff;
|
| 448 |
-
color: white;
|
| 449 |
-
border: none;
|
| 450 |
-
border-radius: 4px;
|
| 451 |
-
cursor: pointer;
|
| 452 |
-
}
|
| 453 |
-
|
| 454 |
-
button:hover {
|
| 455 |
-
background: #0056b3;
|
| 456 |
-
}
|
| 457 |
-
|
| 458 |
-
button:disabled {
|
| 459 |
-
opacity: 0.5;
|
| 460 |
-
cursor: not-allowed;
|
| 461 |
-
}
|
| 462 |
-
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mcp-server.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
| 9 |
} from '@modelcontextprotocol/sdk/types.js';
|
| 10 |
import fetch from 'node-fetch';
|
| 11 |
|
| 12 |
-
const BASE_URL =
|
| 13 |
const API_ENDPOINT = `${BASE_URL}/api/mcp-handler`;
|
| 14 |
|
| 15 |
class ReubenOSMCPServer {
|
|
@@ -43,13 +43,13 @@ class ReubenOSMCPServer {
|
|
| 43 |
tools: [
|
| 44 |
{
|
| 45 |
name: 'save_file',
|
| 46 |
-
description: 'Save a file to Reuben OS.
|
| 47 |
inputSchema: {
|
| 48 |
type: 'object',
|
| 49 |
properties: {
|
| 50 |
fileName: {
|
| 51 |
type: 'string',
|
| 52 |
-
description: 'File name (e.g., main.dart, document.tex, report.pdf
|
| 53 |
},
|
| 54 |
content: {
|
| 55 |
type: 'string',
|
|
@@ -57,7 +57,7 @@ class ReubenOSMCPServer {
|
|
| 57 |
},
|
| 58 |
passkey: {
|
| 59 |
type: 'string',
|
| 60 |
-
description: 'Your passkey for secure storage (min 4 characters). Leave empty to save to public folder.',
|
| 61 |
},
|
| 62 |
isPublic: {
|
| 63 |
type: 'boolean',
|
|
@@ -69,13 +69,13 @@ class ReubenOSMCPServer {
|
|
| 69 |
},
|
| 70 |
{
|
| 71 |
name: 'list_files',
|
| 72 |
-
description: 'List all files in your secure storage or public folder',
|
| 73 |
inputSchema: {
|
| 74 |
type: 'object',
|
| 75 |
properties: {
|
| 76 |
passkey: {
|
| 77 |
type: 'string',
|
| 78 |
-
description: 'Your passkey to list secure files. Leave empty to list public files.',
|
| 79 |
},
|
| 80 |
isPublic: {
|
| 81 |
type: 'boolean',
|
|
@@ -86,7 +86,7 @@ class ReubenOSMCPServer {
|
|
| 86 |
},
|
| 87 |
{
|
| 88 |
name: 'delete_file',
|
| 89 |
-
description: 'Delete a specific file from your storage',
|
| 90 |
inputSchema: {
|
| 91 |
type: 'object',
|
| 92 |
properties: {
|
|
@@ -96,7 +96,7 @@ class ReubenOSMCPServer {
|
|
| 96 |
},
|
| 97 |
passkey: {
|
| 98 |
type: 'string',
|
| 99 |
-
description: 'Your passkey (required for secure files)',
|
| 100 |
},
|
| 101 |
isPublic: {
|
| 102 |
type: 'boolean',
|
|
@@ -108,17 +108,13 @@ class ReubenOSMCPServer {
|
|
| 108 |
},
|
| 109 |
{
|
| 110 |
name: 'deploy_quiz',
|
| 111 |
-
description: 'Deploy an interactive quiz to Reuben OS Quiz App',
|
| 112 |
inputSchema: {
|
| 113 |
type: 'object',
|
| 114 |
properties: {
|
| 115 |
passkey: {
|
| 116 |
type: 'string',
|
| 117 |
-
description: 'Your passkey for secure quiz storage',
|
| 118 |
-
},
|
| 119 |
-
isPublic: {
|
| 120 |
-
type: 'boolean',
|
| 121 |
-
description: 'Make quiz public (default: false)',
|
| 122 |
},
|
| 123 |
quizData: {
|
| 124 |
type: 'object',
|
|
@@ -150,12 +146,12 @@ class ReubenOSMCPServer {
|
|
| 150 |
required: ['title', 'questions'],
|
| 151 |
},
|
| 152 |
},
|
| 153 |
-
required: ['quizData'],
|
| 154 |
},
|
| 155 |
},
|
| 156 |
{
|
| 157 |
name: 'read_file',
|
| 158 |
-
description: 'Read the content of a file from secure storage or public folder. Useful for evaluating quiz answers.',
|
| 159 |
inputSchema: {
|
| 160 |
type: 'object',
|
| 161 |
properties: {
|
|
@@ -165,7 +161,7 @@ class ReubenOSMCPServer {
|
|
| 165 |
},
|
| 166 |
passkey: {
|
| 167 |
type: 'string',
|
| 168 |
-
description: 'Your passkey (required for secure files)',
|
| 169 |
},
|
| 170 |
isPublic: {
|
| 171 |
type: 'boolean',
|
|
@@ -177,7 +173,7 @@ class ReubenOSMCPServer {
|
|
| 177 |
},
|
| 178 |
{
|
| 179 |
name: 'generate_song_audio',
|
| 180 |
-
description: 'Generate an AI song with audio using ElevenLabs Music API.
|
| 181 |
inputSchema: {
|
| 182 |
type: 'object',
|
| 183 |
properties: {
|
|
@@ -199,7 +195,7 @@ class ReubenOSMCPServer {
|
|
| 199 |
},
|
| 200 |
{
|
| 201 |
name: 'generate_story_audio',
|
| 202 |
-
description: 'Generate audio narration for a story using ElevenLabs Text-to-Speech API.
|
| 203 |
inputSchema: {
|
| 204 |
type: 'object',
|
| 205 |
properties: {
|
|
@@ -217,17 +213,13 @@ class ReubenOSMCPServer {
|
|
| 217 |
},
|
| 218 |
{
|
| 219 |
name: 'analyze_quiz',
|
| 220 |
-
description: 'Analyze quiz answers from quiz_answers.json against the quiz.json questions and provide feedback on correctness',
|
| 221 |
inputSchema: {
|
| 222 |
type: 'object',
|
| 223 |
properties: {
|
| 224 |
passkey: {
|
| 225 |
type: 'string',
|
| 226 |
-
description: 'Your passkey for accessing the quiz files',
|
| 227 |
-
},
|
| 228 |
-
isPublic: {
|
| 229 |
-
type: 'boolean',
|
| 230 |
-
description: 'Set to true if quiz files are in public folder. Default: false',
|
| 231 |
},
|
| 232 |
},
|
| 233 |
required: ['passkey'],
|
|
@@ -540,27 +532,29 @@ class ReubenOSMCPServer {
|
|
| 540 |
|
| 541 |
async deployQuiz(args) {
|
| 542 |
try {
|
| 543 |
-
const { quizData, passkey
|
| 544 |
|
| 545 |
-
|
|
|
|
| 546 |
return {
|
| 547 |
-
content: [{ type: 'text', text: 'β
|
| 548 |
};
|
| 549 |
}
|
| 550 |
|
| 551 |
-
if (!
|
| 552 |
return {
|
| 553 |
-
content: [{ type: 'text', text: 'β
|
| 554 |
};
|
| 555 |
}
|
| 556 |
|
| 557 |
const fullQuizData = {
|
| 558 |
...quizData,
|
| 559 |
createdAt: new Date().toISOString(),
|
| 560 |
-
passkey: passkey
|
| 561 |
version: '1.0',
|
| 562 |
};
|
| 563 |
|
|
|
|
| 564 |
const response = await fetch(API_ENDPOINT, {
|
| 565 |
method: 'POST',
|
| 566 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -569,7 +563,7 @@ class ReubenOSMCPServer {
|
|
| 569 |
action: 'deploy_quiz',
|
| 570 |
fileName: 'quiz.json',
|
| 571 |
content: JSON.stringify(fullQuizData, null, 2),
|
| 572 |
-
isPublic,
|
| 573 |
}),
|
| 574 |
});
|
| 575 |
|
|
@@ -577,13 +571,12 @@ class ReubenOSMCPServer {
|
|
| 577 |
|
| 578 |
if (response.ok && data.success) {
|
| 579 |
const totalPoints = quizData.questions.reduce((sum, q) => sum + (q.points || 1), 0);
|
| 580 |
-
const location = isPublic ? 'Public Folder' : `Secure Data (passkey: ${passkey})`;
|
| 581 |
|
| 582 |
return {
|
| 583 |
content: [
|
| 584 |
{
|
| 585 |
type: 'text',
|
| 586 |
-
text: `β
Quiz deployed: ${quizData.title}\nπ ${quizData.questions.length} questions, ${totalPoints} points\n
|
| 587 |
},
|
| 588 |
],
|
| 589 |
};
|
|
@@ -703,18 +696,18 @@ class ReubenOSMCPServer {
|
|
| 703 |
|
| 704 |
async analyzeQuiz(args) {
|
| 705 |
try {
|
| 706 |
-
const { passkey
|
| 707 |
|
| 708 |
-
|
|
|
|
| 709 |
return {
|
| 710 |
-
content: [{ type: 'text', text: 'β Passkey is
|
| 711 |
};
|
| 712 |
}
|
| 713 |
|
| 714 |
-
// Read quiz.json
|
| 715 |
const quizUrl = new URL(API_ENDPOINT);
|
| 716 |
quizUrl.searchParams.set('passkey', passkey);
|
| 717 |
-
if (isPublic) quizUrl.searchParams.set('isPublic', 'true');
|
| 718 |
|
| 719 |
const quizResponse = await fetch(quizUrl, {
|
| 720 |
method: 'GET',
|
|
@@ -763,7 +756,7 @@ class ReubenOSMCPServer {
|
|
| 763 |
if (question.type === 'multiple_choice') {
|
| 764 |
const correctAnswer = question.correctAnswer || question.correct;
|
| 765 |
const isCorrect = userAnswer === correctAnswer ||
|
| 766 |
-
|
| 767 |
|
| 768 |
if (isCorrect) {
|
| 769 |
correctCount++;
|
|
@@ -800,9 +793,9 @@ class ReubenOSMCPServer {
|
|
| 800 |
|
| 801 |
const percentage = Math.round((totalPoints / maxPoints) * 100);
|
| 802 |
const grade = percentage >= 90 ? 'A' :
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
|
| 807 |
return {
|
| 808 |
content: [
|
|
|
|
| 9 |
} from '@modelcontextprotocol/sdk/types.js';
|
| 10 |
import fetch from 'node-fetch';
|
| 11 |
|
| 12 |
+
const BASE_URL = 'https://mcp-1st-birthday-reuben-os.hf.space';
|
| 13 |
const API_ENDPOINT = `${BASE_URL}/api/mcp-handler`;
|
| 14 |
|
| 15 |
class ReubenOSMCPServer {
|
|
|
|
| 43 |
tools: [
|
| 44 |
{
|
| 45 |
name: 'save_file',
|
| 46 |
+
description: 'Save a file to Reuben OS. Passkey is OPTIONAL - provide for secure storage or leave empty for public folder.',
|
| 47 |
inputSchema: {
|
| 48 |
type: 'object',
|
| 49 |
properties: {
|
| 50 |
fileName: {
|
| 51 |
type: 'string',
|
| 52 |
+
description: 'File name (e.g., main.dart, document.tex, report.pdf)',
|
| 53 |
},
|
| 54 |
content: {
|
| 55 |
type: 'string',
|
|
|
|
| 57 |
},
|
| 58 |
passkey: {
|
| 59 |
type: 'string',
|
| 60 |
+
description: 'OPTIONAL: Your passkey for secure storage (min 4 characters). Leave empty to save to public folder.',
|
| 61 |
},
|
| 62 |
isPublic: {
|
| 63 |
type: 'boolean',
|
|
|
|
| 69 |
},
|
| 70 |
{
|
| 71 |
name: 'list_files',
|
| 72 |
+
description: 'List all files in your secure storage or public folder. Passkey is OPTIONAL.',
|
| 73 |
inputSchema: {
|
| 74 |
type: 'object',
|
| 75 |
properties: {
|
| 76 |
passkey: {
|
| 77 |
type: 'string',
|
| 78 |
+
description: 'OPTIONAL: Your passkey to list secure files. Leave empty to list public files.',
|
| 79 |
},
|
| 80 |
isPublic: {
|
| 81 |
type: 'boolean',
|
|
|
|
| 86 |
},
|
| 87 |
{
|
| 88 |
name: 'delete_file',
|
| 89 |
+
description: 'Delete a specific file from your storage. Passkey is OPTIONAL.',
|
| 90 |
inputSchema: {
|
| 91 |
type: 'object',
|
| 92 |
properties: {
|
|
|
|
| 96 |
},
|
| 97 |
passkey: {
|
| 98 |
type: 'string',
|
| 99 |
+
description: 'OPTIONAL: Your passkey (required for secure files). Leave empty for public files.',
|
| 100 |
},
|
| 101 |
isPublic: {
|
| 102 |
type: 'boolean',
|
|
|
|
| 108 |
},
|
| 109 |
{
|
| 110 |
name: 'deploy_quiz',
|
| 111 |
+
description: 'Deploy an interactive quiz to Reuben OS Quiz App. Passkey is REQUIRED for all quizzes.',
|
| 112 |
inputSchema: {
|
| 113 |
type: 'object',
|
| 114 |
properties: {
|
| 115 |
passkey: {
|
| 116 |
type: 'string',
|
| 117 |
+
description: 'Your passkey for secure quiz storage (REQUIRED - min 4 characters)',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
},
|
| 119 |
quizData: {
|
| 120 |
type: 'object',
|
|
|
|
| 146 |
required: ['title', 'questions'],
|
| 147 |
},
|
| 148 |
},
|
| 149 |
+
required: ['passkey', 'quizData'],
|
| 150 |
},
|
| 151 |
},
|
| 152 |
{
|
| 153 |
name: 'read_file',
|
| 154 |
+
description: 'Read the content of a file from secure storage or public folder. Passkey is OPTIONAL. Useful for evaluating quiz answers.',
|
| 155 |
inputSchema: {
|
| 156 |
type: 'object',
|
| 157 |
properties: {
|
|
|
|
| 161 |
},
|
| 162 |
passkey: {
|
| 163 |
type: 'string',
|
| 164 |
+
description: 'OPTIONAL: Your passkey (required for secure files). Leave empty for public files.',
|
| 165 |
},
|
| 166 |
isPublic: {
|
| 167 |
type: 'boolean',
|
|
|
|
| 173 |
},
|
| 174 |
{
|
| 175 |
name: 'generate_song_audio',
|
| 176 |
+
description: 'Generate an AI song with audio using ElevenLabs Music API. NO passkey required - saves directly to Voice Studio app.',
|
| 177 |
inputSchema: {
|
| 178 |
type: 'object',
|
| 179 |
properties: {
|
|
|
|
| 195 |
},
|
| 196 |
{
|
| 197 |
name: 'generate_story_audio',
|
| 198 |
+
description: 'Generate audio narration for a story using ElevenLabs Text-to-Speech API. NO passkey required - saves directly to Voice Studio app.',
|
| 199 |
inputSchema: {
|
| 200 |
type: 'object',
|
| 201 |
properties: {
|
|
|
|
| 213 |
},
|
| 214 |
{
|
| 215 |
name: 'analyze_quiz',
|
| 216 |
+
description: 'Analyze quiz answers from quiz_answers.json against the quiz.json questions and provide feedback on correctness. Passkey is REQUIRED.',
|
| 217 |
inputSchema: {
|
| 218 |
type: 'object',
|
| 219 |
properties: {
|
| 220 |
passkey: {
|
| 221 |
type: 'string',
|
| 222 |
+
description: 'Your passkey for accessing the quiz files (REQUIRED)',
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
},
|
| 224 |
},
|
| 225 |
required: ['passkey'],
|
|
|
|
| 532 |
|
| 533 |
async deployQuiz(args) {
|
| 534 |
try {
|
| 535 |
+
const { quizData, passkey } = args;
|
| 536 |
|
| 537 |
+
// Passkey is REQUIRED for quizzes
|
| 538 |
+
if (!passkey || passkey.length < 4) {
|
| 539 |
return {
|
| 540 |
+
content: [{ type: 'text', text: 'β Passkey is REQUIRED for quizzes (minimum 4 characters)' }],
|
| 541 |
};
|
| 542 |
}
|
| 543 |
|
| 544 |
+
if (!quizData || !quizData.questions || quizData.questions.length === 0) {
|
| 545 |
return {
|
| 546 |
+
content: [{ type: 'text', text: 'β Quiz must have at least one question' }],
|
| 547 |
};
|
| 548 |
}
|
| 549 |
|
| 550 |
const fullQuizData = {
|
| 551 |
...quizData,
|
| 552 |
createdAt: new Date().toISOString(),
|
| 553 |
+
passkey: passkey,
|
| 554 |
version: '1.0',
|
| 555 |
};
|
| 556 |
|
| 557 |
+
// Quizzes are always saved securely with passkey, never public
|
| 558 |
const response = await fetch(API_ENDPOINT, {
|
| 559 |
method: 'POST',
|
| 560 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 563 |
action: 'deploy_quiz',
|
| 564 |
fileName: 'quiz.json',
|
| 565 |
content: JSON.stringify(fullQuizData, null, 2),
|
| 566 |
+
isPublic: false, // Always secure for quizzes
|
| 567 |
}),
|
| 568 |
});
|
| 569 |
|
|
|
|
| 571 |
|
| 572 |
if (response.ok && data.success) {
|
| 573 |
const totalPoints = quizData.questions.reduce((sum, q) => sum + (q.points || 1), 0);
|
|
|
|
| 574 |
|
| 575 |
return {
|
| 576 |
content: [
|
| 577 |
{
|
| 578 |
type: 'text',
|
| 579 |
+
text: `β
Quiz deployed: ${quizData.title}\nπ ${quizData.questions.length} questions, ${totalPoints} points\nπ Secured with passkey: ${passkey}`,
|
| 580 |
},
|
| 581 |
],
|
| 582 |
};
|
|
|
|
| 696 |
|
| 697 |
async analyzeQuiz(args) {
|
| 698 |
try {
|
| 699 |
+
const { passkey } = args;
|
| 700 |
|
| 701 |
+
// Passkey is REQUIRED for quiz analysis
|
| 702 |
+
if (!passkey || passkey.length < 4) {
|
| 703 |
return {
|
| 704 |
+
content: [{ type: 'text', text: 'β Passkey is REQUIRED to access quiz files (minimum 4 characters)' }],
|
| 705 |
};
|
| 706 |
}
|
| 707 |
|
| 708 |
+
// Read quiz.json (quizzes are always secure, never public)
|
| 709 |
const quizUrl = new URL(API_ENDPOINT);
|
| 710 |
quizUrl.searchParams.set('passkey', passkey);
|
|
|
|
| 711 |
|
| 712 |
const quizResponse = await fetch(quizUrl, {
|
| 713 |
method: 'GET',
|
|
|
|
| 756 |
if (question.type === 'multiple_choice') {
|
| 757 |
const correctAnswer = question.correctAnswer || question.correct;
|
| 758 |
const isCorrect = userAnswer === correctAnswer ||
|
| 759 |
+
(Array.isArray(correctAnswer) && correctAnswer.includes(userAnswer));
|
| 760 |
|
| 761 |
if (isCorrect) {
|
| 762 |
correctCount++;
|
|
|
|
| 793 |
|
| 794 |
const percentage = Math.round((totalPoints / maxPoints) * 100);
|
| 795 |
const grade = percentage >= 90 ? 'A' :
|
| 796 |
+
percentage >= 80 ? 'B' :
|
| 797 |
+
percentage >= 70 ? 'C' :
|
| 798 |
+
percentage >= 60 ? 'D' : 'F';
|
| 799 |
|
| 800 |
return {
|
| 801 |
content: [
|
package-lock.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
| 16 |
"@radix-ui/colors": "^3.0.0",
|
| 17 |
"@types/katex": "^0.16.7",
|
| 18 |
"docx": "^9.5.1",
|
|
|
|
| 19 |
"exceljs": "^4.4.0",
|
| 20 |
"framer-motion": "^12.23.24",
|
| 21 |
"highlight.js": "^11.11.1",
|
|
@@ -1743,6 +1744,18 @@
|
|
| 1743 |
"url": "https://github.com/sponsors/epoberezkin"
|
| 1744 |
}
|
| 1745 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1746 |
"node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
|
| 1747 |
"version": "1.0.0",
|
| 1748 |
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
@@ -6487,15 +6500,15 @@
|
|
| 6487 |
}
|
| 6488 |
},
|
| 6489 |
"node_modules/eventsource": {
|
| 6490 |
-
"version": "
|
| 6491 |
-
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-
|
| 6492 |
-
"integrity": "sha512-
|
| 6493 |
"license": "MIT",
|
| 6494 |
"dependencies": {
|
| 6495 |
"eventsource-parser": "^3.0.1"
|
| 6496 |
},
|
| 6497 |
"engines": {
|
| 6498 |
-
"node": ">=
|
| 6499 |
}
|
| 6500 |
},
|
| 6501 |
"node_modules/eventsource-parser": {
|
|
|
|
| 16 |
"@radix-ui/colors": "^3.0.0",
|
| 17 |
"@types/katex": "^0.16.7",
|
| 18 |
"docx": "^9.5.1",
|
| 19 |
+
"eventsource": "^4.1.0",
|
| 20 |
"exceljs": "^4.4.0",
|
| 21 |
"framer-motion": "^12.23.24",
|
| 22 |
"highlight.js": "^11.11.1",
|
|
|
|
| 1744 |
"url": "https://github.com/sponsors/epoberezkin"
|
| 1745 |
}
|
| 1746 |
},
|
| 1747 |
+
"node_modules/@modelcontextprotocol/sdk/node_modules/eventsource": {
|
| 1748 |
+
"version": "3.0.7",
|
| 1749 |
+
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
|
| 1750 |
+
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
|
| 1751 |
+
"license": "MIT",
|
| 1752 |
+
"dependencies": {
|
| 1753 |
+
"eventsource-parser": "^3.0.1"
|
| 1754 |
+
},
|
| 1755 |
+
"engines": {
|
| 1756 |
+
"node": ">=18.0.0"
|
| 1757 |
+
}
|
| 1758 |
+
},
|
| 1759 |
"node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
|
| 1760 |
"version": "1.0.0",
|
| 1761 |
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
|
|
| 6500 |
}
|
| 6501 |
},
|
| 6502 |
"node_modules/eventsource": {
|
| 6503 |
+
"version": "4.1.0",
|
| 6504 |
+
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz",
|
| 6505 |
+
"integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==",
|
| 6506 |
"license": "MIT",
|
| 6507 |
"dependencies": {
|
| 6508 |
"eventsource-parser": "^3.0.1"
|
| 6509 |
},
|
| 6510 |
"engines": {
|
| 6511 |
+
"node": ">=20.0.0"
|
| 6512 |
}
|
| 6513 |
},
|
| 6514 |
"node_modules/eventsource-parser": {
|
package.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
| 20 |
"@radix-ui/colors": "^3.0.0",
|
| 21 |
"@types/katex": "^0.16.7",
|
| 22 |
"docx": "^9.5.1",
|
|
|
|
| 23 |
"exceljs": "^4.4.0",
|
| 24 |
"framer-motion": "^12.23.24",
|
| 25 |
"highlight.js": "^11.11.1",
|
|
|
|
| 20 |
"@radix-ui/colors": "^3.0.0",
|
| 21 |
"@types/katex": "^0.16.7",
|
| 22 |
"docx": "^9.5.1",
|
| 23 |
+
"eventsource": "^4.1.0",
|
| 24 |
"exceljs": "^4.4.0",
|
| 25 |
"framer-motion": "^12.23.24",
|
| 26 |
"highlight.js": "^11.11.1",
|
pages/api/mcp.ts
DELETED
|
@@ -1,406 +0,0 @@
|
|
| 1 |
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
| 2 |
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
| 3 |
-
import {
|
| 4 |
-
CallToolRequestSchema,
|
| 5 |
-
ListToolsRequestSchema,
|
| 6 |
-
} from '@modelcontextprotocol/sdk/types.js';
|
| 7 |
-
import fs from 'fs';
|
| 8 |
-
import path from 'path';
|
| 9 |
-
import fetch from 'node-fetch';
|
| 10 |
-
|
| 11 |
-
declare global {
|
| 12 |
-
var mcpTransports: Map<string, any>;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
// Global state to store active transports
|
| 16 |
-
// In a serverless environment (like Vercel), this might not work reliably for long-lived connections across requests.
|
| 17 |
-
// But for a persistent container (Hugging Face Spaces), this is fine.
|
| 18 |
-
if (!global.mcpTransports) {
|
| 19 |
-
global.mcpTransports = new Map();
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
// Configuration
|
| 23 |
-
const DATA_DIR = process.env.SPACE_ID ? '/data' : path.join(process.cwd(), 'public', 'data');
|
| 24 |
-
const PUBLIC_DIR = path.join(DATA_DIR, 'public');
|
| 25 |
-
const BASE_URL = process.env.SPACE_ID ? 'https://mcp-1st-birthday-reuben-os.hf.space' : 'http://localhost:3000';
|
| 26 |
-
|
| 27 |
-
// Helper functions
|
| 28 |
-
function ensureDirectories() {
|
| 29 |
-
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
|
| 30 |
-
if (!fs.existsSync(PUBLIC_DIR)) fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
function isValidPasskey(passkey: string): boolean {
|
| 34 |
-
return !!passkey && /^[a-zA-Z0-9_-]+$/.test(passkey) && passkey.length >= 4;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
function sanitizeFileName(fileName: string): string {
|
| 38 |
-
return fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
function sanitizePasskey(passkey: string): string {
|
| 42 |
-
return passkey.replace(/[^a-zA-Z0-9_-]/g, '');
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
import { NextApiRequest, NextApiResponse } from 'next';
|
| 46 |
-
|
| 47 |
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
| 48 |
-
// Enable CORS
|
| 49 |
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
| 50 |
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
| 51 |
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
| 52 |
-
|
| 53 |
-
if (req.method === 'OPTIONS') {
|
| 54 |
-
return res.status(200).end();
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
ensureDirectories();
|
| 58 |
-
|
| 59 |
-
if (req.method === 'GET') {
|
| 60 |
-
// Handle SSE Connection
|
| 61 |
-
// Critical headers for SSE on hosting platforms (like HF Spaces)
|
| 62 |
-
res.setHeader('Content-Type', 'text/event-stream');
|
| 63 |
-
res.setHeader('Cache-Control', 'no-cache');
|
| 64 |
-
res.setHeader('Connection', 'keep-alive');
|
| 65 |
-
res.setHeader('X-Accel-Buffering', 'no'); // Disable buffering for Nginx/HF
|
| 66 |
-
|
| 67 |
-
const transport = new SSEServerTransport(`${BASE_URL}/api/mcp`, res);
|
| 68 |
-
|
| 69 |
-
// Store transport IMMEDIATELY to avoid race condition
|
| 70 |
-
// (Client might connect and POST before server.connect() returns)
|
| 71 |
-
if (transport.sessionId) {
|
| 72 |
-
global.mcpTransports.set(transport.sessionId, transport);
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
// Create a new server instance for this connection
|
| 76 |
-
const server = new Server(
|
| 77 |
-
{
|
| 78 |
-
name: 'reubenos-mcp-server',
|
| 79 |
-
version: '3.0.0',
|
| 80 |
-
},
|
| 81 |
-
{
|
| 82 |
-
capabilities: {
|
| 83 |
-
tools: {},
|
| 84 |
-
},
|
| 85 |
-
}
|
| 86 |
-
);
|
| 87 |
-
|
| 88 |
-
// Setup tools
|
| 89 |
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
| 90 |
-
tools: [
|
| 91 |
-
{
|
| 92 |
-
name: 'save_file',
|
| 93 |
-
description: 'Save a file to Reuben OS. Use your passkey for secure storage or save to public folder.',
|
| 94 |
-
inputSchema: {
|
| 95 |
-
type: 'object',
|
| 96 |
-
properties: {
|
| 97 |
-
fileName: { type: 'string', description: 'File name' },
|
| 98 |
-
content: { type: 'string', description: 'File content' },
|
| 99 |
-
passkey: { type: 'string', description: 'Passkey for secure storage' },
|
| 100 |
-
isPublic: { type: 'boolean', description: 'Save to public folder' },
|
| 101 |
-
},
|
| 102 |
-
required: ['fileName', 'content'],
|
| 103 |
-
},
|
| 104 |
-
},
|
| 105 |
-
{
|
| 106 |
-
name: 'list_files',
|
| 107 |
-
description: 'List all files in your secure storage or public folder',
|
| 108 |
-
inputSchema: {
|
| 109 |
-
type: 'object',
|
| 110 |
-
properties: {
|
| 111 |
-
passkey: { type: 'string', description: 'Passkey' },
|
| 112 |
-
isPublic: { type: 'boolean', description: 'List public files' },
|
| 113 |
-
},
|
| 114 |
-
},
|
| 115 |
-
},
|
| 116 |
-
{
|
| 117 |
-
name: 'delete_file',
|
| 118 |
-
description: 'Delete a specific file',
|
| 119 |
-
inputSchema: {
|
| 120 |
-
type: 'object',
|
| 121 |
-
properties: {
|
| 122 |
-
fileName: { type: 'string' },
|
| 123 |
-
passkey: { type: 'string' },
|
| 124 |
-
isPublic: { type: 'boolean' },
|
| 125 |
-
},
|
| 126 |
-
required: ['fileName'],
|
| 127 |
-
},
|
| 128 |
-
},
|
| 129 |
-
{
|
| 130 |
-
name: 'read_file',
|
| 131 |
-
description: 'Read the content of a file',
|
| 132 |
-
inputSchema: {
|
| 133 |
-
type: 'object',
|
| 134 |
-
properties: {
|
| 135 |
-
fileName: { type: 'string' },
|
| 136 |
-
passkey: { type: 'string' },
|
| 137 |
-
isPublic: { type: 'boolean' },
|
| 138 |
-
},
|
| 139 |
-
required: ['fileName'],
|
| 140 |
-
},
|
| 141 |
-
},
|
| 142 |
-
{
|
| 143 |
-
name: 'generate_song_audio',
|
| 144 |
-
description: 'Generate an AI song with audio',
|
| 145 |
-
inputSchema: {
|
| 146 |
-
type: 'object',
|
| 147 |
-
properties: {
|
| 148 |
-
title: { type: 'string' },
|
| 149 |
-
style: { type: 'string' },
|
| 150 |
-
lyrics: { type: 'string' },
|
| 151 |
-
},
|
| 152 |
-
required: ['title', 'style', 'lyrics'],
|
| 153 |
-
},
|
| 154 |
-
},
|
| 155 |
-
{
|
| 156 |
-
name: 'generate_story_audio',
|
| 157 |
-
description: 'Generate audio narration for a story',
|
| 158 |
-
inputSchema: {
|
| 159 |
-
type: 'object',
|
| 160 |
-
properties: {
|
| 161 |
-
title: { type: 'string' },
|
| 162 |
-
content: { type: 'string' },
|
| 163 |
-
},
|
| 164 |
-
required: ['title', 'content'],
|
| 165 |
-
},
|
| 166 |
-
},
|
| 167 |
-
{
|
| 168 |
-
name: 'deploy_quiz',
|
| 169 |
-
description: 'Deploy an interactive quiz to Reuben OS Quiz App',
|
| 170 |
-
inputSchema: {
|
| 171 |
-
type: 'object',
|
| 172 |
-
properties: {
|
| 173 |
-
passkey: { type: 'string', description: 'Passkey for secure storage' },
|
| 174 |
-
isPublic: { type: 'boolean', description: 'Make quiz public' },
|
| 175 |
-
quizData: {
|
| 176 |
-
type: 'object',
|
| 177 |
-
description: 'Quiz configuration',
|
| 178 |
-
properties: {
|
| 179 |
-
title: { type: 'string' },
|
| 180 |
-
description: { type: 'string' },
|
| 181 |
-
questions: { type: 'array' },
|
| 182 |
-
timeLimit: { type: 'number' },
|
| 183 |
-
},
|
| 184 |
-
required: ['title', 'questions'],
|
| 185 |
-
},
|
| 186 |
-
},
|
| 187 |
-
required: ['quizData'],
|
| 188 |
-
},
|
| 189 |
-
},
|
| 190 |
-
{
|
| 191 |
-
name: 'analyze_quiz',
|
| 192 |
-
description: 'Analyze quiz answers and provide feedback',
|
| 193 |
-
inputSchema: {
|
| 194 |
-
type: 'object',
|
| 195 |
-
properties: {
|
| 196 |
-
passkey: { type: 'string', description: 'Passkey' },
|
| 197 |
-
isPublic: { type: 'boolean', description: 'Quiz files in public folder' },
|
| 198 |
-
},
|
| 199 |
-
required: ['passkey'],
|
| 200 |
-
},
|
| 201 |
-
},
|
| 202 |
-
],
|
| 203 |
-
}));
|
| 204 |
-
|
| 205 |
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
| 206 |
-
const { name, arguments: args } = request.params as { name: string, arguments: any };
|
| 207 |
-
|
| 208 |
-
try {
|
| 209 |
-
switch (name) {
|
| 210 |
-
case 'save_file': {
|
| 211 |
-
const { fileName, content, passkey, isPublic } = args;
|
| 212 |
-
if (!isPublic && !passkey) throw new Error('Passkey required');
|
| 213 |
-
|
| 214 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 215 |
-
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
| 216 |
-
|
| 217 |
-
const filePath = path.join(targetDir, sanitizeFileName(fileName));
|
| 218 |
-
fs.writeFileSync(filePath, typeof content === 'string' ? content : JSON.stringify(content, null, 2));
|
| 219 |
-
|
| 220 |
-
return {
|
| 221 |
-
content: [{ type: 'text', text: `Saved ${fileName} to ${isPublic ? 'Public' : 'Secure'} storage.` }],
|
| 222 |
-
};
|
| 223 |
-
}
|
| 224 |
-
case 'list_files': {
|
| 225 |
-
const { passkey, isPublic } = args;
|
| 226 |
-
if (!isPublic && !passkey) throw new Error('Passkey required');
|
| 227 |
-
|
| 228 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 229 |
-
if (!fs.existsSync(targetDir)) return { content: [{ type: 'text', text: 'No files found.' }] };
|
| 230 |
-
|
| 231 |
-
const files = fs.readdirSync(targetDir);
|
| 232 |
-
return {
|
| 233 |
-
content: [{ type: 'text', text: `Files:\n${files.join('\n')}` }],
|
| 234 |
-
};
|
| 235 |
-
}
|
| 236 |
-
case 'delete_file': {
|
| 237 |
-
const { fileName, passkey, isPublic } = args;
|
| 238 |
-
if (!isPublic && !passkey) throw new Error('Passkey required');
|
| 239 |
-
|
| 240 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 241 |
-
const filePath = path.join(targetDir, sanitizeFileName(fileName));
|
| 242 |
-
|
| 243 |
-
if (fs.existsSync(filePath)) {
|
| 244 |
-
fs.unlinkSync(filePath);
|
| 245 |
-
return { content: [{ type: 'text', text: `Deleted ${fileName}` }] };
|
| 246 |
-
}
|
| 247 |
-
return { content: [{ type: 'text', text: 'File not found' }] };
|
| 248 |
-
}
|
| 249 |
-
case 'read_file': {
|
| 250 |
-
const { fileName, passkey, isPublic } = args;
|
| 251 |
-
if (!isPublic && !passkey) throw new Error('Passkey required');
|
| 252 |
-
|
| 253 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 254 |
-
const filePath = path.join(targetDir, sanitizeFileName(fileName));
|
| 255 |
-
|
| 256 |
-
if (fs.existsSync(filePath)) {
|
| 257 |
-
const content = fs.readFileSync(filePath, 'utf8');
|
| 258 |
-
return { content: [{ type: 'text', text: content }] };
|
| 259 |
-
}
|
| 260 |
-
return { content: [{ type: 'text', text: 'File not found' }] };
|
| 261 |
-
}
|
| 262 |
-
case 'generate_song_audio': {
|
| 263 |
-
// Call internal API
|
| 264 |
-
const response = await fetch(`${BASE_URL}/api/voice/generate-song`, {
|
| 265 |
-
method: 'POST',
|
| 266 |
-
headers: { 'Content-Type': 'application/json' },
|
| 267 |
-
body: JSON.stringify({ ...args, passkey: 'voice_default' }),
|
| 268 |
-
});
|
| 269 |
-
const data = await response.json() as any;
|
| 270 |
-
|
| 271 |
-
// Save result
|
| 272 |
-
if (data.success) {
|
| 273 |
-
await fetch(`${BASE_URL}/api/voice/save`, {
|
| 274 |
-
method: 'POST',
|
| 275 |
-
headers: { 'Content-Type': 'application/json' },
|
| 276 |
-
body: JSON.stringify({ passkey: 'voice_default', content: data.content }),
|
| 277 |
-
});
|
| 278 |
-
return { content: [{ type: 'text', text: 'Song generated and saved to Voice Studio.' }] };
|
| 279 |
-
}
|
| 280 |
-
throw new Error(data.error || 'Failed to generate song');
|
| 281 |
-
}
|
| 282 |
-
case 'generate_story_audio': {
|
| 283 |
-
const response = await fetch(`${BASE_URL}/api/voice/generate-story`, {
|
| 284 |
-
method: 'POST',
|
| 285 |
-
headers: { 'Content-Type': 'application/json' },
|
| 286 |
-
body: JSON.stringify({ ...args, passkey: 'voice_default' }),
|
| 287 |
-
});
|
| 288 |
-
const data = await response.json() as any;
|
| 289 |
-
|
| 290 |
-
if (data.success) {
|
| 291 |
-
await fetch(`${BASE_URL}/api/voice/save`, {
|
| 292 |
-
method: 'POST',
|
| 293 |
-
headers: { 'Content-Type': 'application/json' },
|
| 294 |
-
body: JSON.stringify({ passkey: 'voice_default', content: data.content }),
|
| 295 |
-
});
|
| 296 |
-
return { content: [{ type: 'text', text: 'Story generated and saved to Voice Studio.' }] };
|
| 297 |
-
}
|
| 298 |
-
throw new Error(data.error || 'Failed to generate story');
|
| 299 |
-
}
|
| 300 |
-
case 'deploy_quiz': {
|
| 301 |
-
const { quizData, passkey, isPublic } = args;
|
| 302 |
-
if (!isPublic && !passkey) throw new Error('Passkey required');
|
| 303 |
-
|
| 304 |
-
const fullQuizData = {
|
| 305 |
-
...quizData,
|
| 306 |
-
createdAt: new Date().toISOString(),
|
| 307 |
-
passkey: passkey || 'public',
|
| 308 |
-
version: '1.0',
|
| 309 |
-
};
|
| 310 |
-
|
| 311 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 312 |
-
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
| 313 |
-
|
| 314 |
-
const filePath = path.join(targetDir, 'quiz.json');
|
| 315 |
-
fs.writeFileSync(filePath, JSON.stringify(fullQuizData, null, 2));
|
| 316 |
-
|
| 317 |
-
const totalPoints = quizData.questions.reduce((sum: number, q: any) => sum + (q.points || 1), 0);
|
| 318 |
-
return {
|
| 319 |
-
content: [{ type: 'text', text: `Quiz deployed: ${quizData.title}\n${quizData.questions.length} questions, ${totalPoints} points\nSaved to: ${isPublic ? 'Public' : 'Secure'} storage` }],
|
| 320 |
-
};
|
| 321 |
-
}
|
| 322 |
-
case 'analyze_quiz': {
|
| 323 |
-
const { passkey, isPublic } = args;
|
| 324 |
-
if (!passkey) throw new Error('Passkey required');
|
| 325 |
-
|
| 326 |
-
const targetDir = isPublic ? PUBLIC_DIR : path.join(DATA_DIR, sanitizePasskey(passkey));
|
| 327 |
-
|
| 328 |
-
const quizPath = path.join(targetDir, 'quiz.json');
|
| 329 |
-
const answersPath = path.join(targetDir, 'quiz_answers.json');
|
| 330 |
-
|
| 331 |
-
if (!fs.existsSync(quizPath)) return { content: [{ type: 'text', text: 'quiz.json not found' }] };
|
| 332 |
-
if (!fs.existsSync(answersPath)) return { content: [{ type: 'text', text: 'quiz_answers.json not found' }] };
|
| 333 |
-
|
| 334 |
-
const quiz = JSON.parse(fs.readFileSync(quizPath, 'utf8'));
|
| 335 |
-
const answers = JSON.parse(fs.readFileSync(answersPath, 'utf8'));
|
| 336 |
-
|
| 337 |
-
let correctCount = 0;
|
| 338 |
-
let totalPoints = 0;
|
| 339 |
-
let maxPoints = 0;
|
| 340 |
-
const feedback: string[] = [];
|
| 341 |
-
|
| 342 |
-
quiz.questions.forEach((question: any, index: number) => {
|
| 343 |
-
const userAnswer = answers.answers?.[question.id] || answers[question.id];
|
| 344 |
-
const points = question.points || 1;
|
| 345 |
-
maxPoints += points;
|
| 346 |
-
|
| 347 |
-
const correctAnswer = question.correctAnswer || question.correct;
|
| 348 |
-
const isCorrect = userAnswer === correctAnswer ||
|
| 349 |
-
(Array.isArray(correctAnswer) && correctAnswer.includes(userAnswer));
|
| 350 |
-
|
| 351 |
-
if (isCorrect) {
|
| 352 |
-
correctCount++;
|
| 353 |
-
totalPoints += points;
|
| 354 |
-
feedback.push(`β
Question ${index + 1}: Correct! (+${points} points)`);
|
| 355 |
-
} else {
|
| 356 |
-
feedback.push(`β Question ${index + 1}: Incorrect. Your answer: "${userAnswer}"${question.explanation ? `. ${question.explanation}` : ''}`);
|
| 357 |
-
}
|
| 358 |
-
});
|
| 359 |
-
|
| 360 |
-
const percentage = ((totalPoints / maxPoints) * 100).toFixed(1);
|
| 361 |
-
const result = `Quiz Results:\nScore: ${totalPoints}/${maxPoints} (${percentage}%)\nCorrect: ${correctCount}/${quiz.questions.length}\n\n${feedback.join('\n')}`;
|
| 362 |
-
|
| 363 |
-
return { content: [{ type: 'text', text: result }] };
|
| 364 |
-
}
|
| 365 |
-
default:
|
| 366 |
-
throw new Error(`Unknown tool: ${name}`);
|
| 367 |
-
}
|
| 368 |
-
} catch (error: any) {
|
| 369 |
-
return {
|
| 370 |
-
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
| 371 |
-
isError: true,
|
| 372 |
-
};
|
| 373 |
-
}
|
| 374 |
-
});
|
| 375 |
-
|
| 376 |
-
// Store transport in global map using sessionId
|
| 377 |
-
// We need to wait for the transport to be ready or just store it.
|
| 378 |
-
// SSEServerTransport generates a sessionId.
|
| 379 |
-
// We can access it via `transport.sessionId` AFTER `start()` or `connect()`?
|
| 380 |
-
// Actually, `SSEServerTransport` handles the request in `start()`.
|
| 381 |
-
|
| 382 |
-
await server.connect(transport);
|
| 383 |
-
|
| 384 |
-
// Clean up on close
|
| 385 |
-
transport.onclose = () => {
|
| 386 |
-
if (transport.sessionId) {
|
| 387 |
-
global.mcpTransports.delete(transport.sessionId);
|
| 388 |
-
}
|
| 389 |
-
};
|
| 390 |
-
|
| 391 |
-
} else if (req.method === 'POST') {
|
| 392 |
-
const sessionId = Array.isArray(req.query.sessionId) ? req.query.sessionId[0] : req.query.sessionId;
|
| 393 |
-
if (!sessionId) {
|
| 394 |
-
return res.status(400).send('Missing sessionId');
|
| 395 |
-
}
|
| 396 |
-
|
| 397 |
-
const transport = global.mcpTransports.get(sessionId);
|
| 398 |
-
if (!transport) {
|
| 399 |
-
return res.status(404).send('Session not found');
|
| 400 |
-
}
|
| 401 |
-
|
| 402 |
-
await transport.handlePostMessage(req, res);
|
| 403 |
-
} else {
|
| 404 |
-
res.status(405).send('Method not allowed');
|
| 405 |
-
}
|
| 406 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-api.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 1 |
-
// test-api.js - Test script to verify the API is working
|
| 2 |
-
// Run this with: node test-api.js
|
| 3 |
-
|
| 4 |
-
const API_URL = process.env.REUBENOS_URL || 'https://mcp-1st-birthday-reuben-os.hf.space';
|
| 5 |
-
|
| 6 |
-
async function testAPI() {
|
| 7 |
-
const sessionId = 'session_1763722877048_527d6bb8b7473568';
|
| 8 |
-
|
| 9 |
-
console.log('π Testing Reuben OS API...\n');
|
| 10 |
-
console.log(`API URL: ${API_URL}`);
|
| 11 |
-
console.log(`Session ID: ${sessionId}\n`);
|
| 12 |
-
|
| 13 |
-
try {
|
| 14 |
-
// Test 1: Save a regular file
|
| 15 |
-
console.log('π Test 1: Saving a regular file...');
|
| 16 |
-
const saveResponse = await fetch(`${API_URL}/api/mcp-handler`, {
|
| 17 |
-
method: 'POST',
|
| 18 |
-
headers: { 'Content-Type': 'application/json' },
|
| 19 |
-
body: JSON.stringify({
|
| 20 |
-
sessionId: sessionId,
|
| 21 |
-
action: 'save_file',
|
| 22 |
-
fileName: 'test.txt',
|
| 23 |
-
content: 'This is a test file created at ' + new Date().toISOString()
|
| 24 |
-
})
|
| 25 |
-
});
|
| 26 |
-
|
| 27 |
-
const saveData = await saveResponse.json();
|
| 28 |
-
console.log('Save response:', saveData);
|
| 29 |
-
console.log('β
File save:', saveData.success ? 'SUCCESS' : 'FAILED');
|
| 30 |
-
console.log('');
|
| 31 |
-
|
| 32 |
-
// Test 2: Deploy a quiz
|
| 33 |
-
console.log('π― Test 2: Deploying a quiz...');
|
| 34 |
-
const quizData = {
|
| 35 |
-
title: 'Test Quiz',
|
| 36 |
-
description: 'A simple test quiz',
|
| 37 |
-
questions: [
|
| 38 |
-
{
|
| 39 |
-
id: 'q1',
|
| 40 |
-
question: 'Is this working?',
|
| 41 |
-
type: 'true-false',
|
| 42 |
-
correctAnswer: true,
|
| 43 |
-
points: 1
|
| 44 |
-
}
|
| 45 |
-
]
|
| 46 |
-
};
|
| 47 |
-
|
| 48 |
-
const quizResponse = await fetch(`${API_URL}/api/mcp-handler`, {
|
| 49 |
-
method: 'POST',
|
| 50 |
-
headers: { 'Content-Type': 'application/json' },
|
| 51 |
-
body: JSON.stringify({
|
| 52 |
-
sessionId: sessionId,
|
| 53 |
-
action: 'deploy_quiz',
|
| 54 |
-
fileName: 'quiz.json',
|
| 55 |
-
content: JSON.stringify(quizData, null, 2)
|
| 56 |
-
})
|
| 57 |
-
});
|
| 58 |
-
|
| 59 |
-
const quizResponseData = await quizResponse.json();
|
| 60 |
-
console.log('Quiz response:', quizResponseData);
|
| 61 |
-
console.log('β
Quiz deploy:', quizResponseData.success ? 'SUCCESS' : 'FAILED');
|
| 62 |
-
console.log('');
|
| 63 |
-
|
| 64 |
-
// Test 3: Retrieve files
|
| 65 |
-
console.log('π Test 3: Retrieving files for session...');
|
| 66 |
-
const getResponse = await fetch(`${API_URL}/api/mcp-handler?sessionId=${sessionId}`, {
|
| 67 |
-
method: 'GET',
|
| 68 |
-
headers: { 'Content-Type': 'application/json' }
|
| 69 |
-
});
|
| 70 |
-
|
| 71 |
-
const getData = await getResponse.json();
|
| 72 |
-
console.log('Retrieve response:', {
|
| 73 |
-
success: getData.success,
|
| 74 |
-
fileCount: getData.count,
|
| 75 |
-
files: getData.files?.map(f => ({
|
| 76 |
-
name: f.name,
|
| 77 |
-
size: f.size,
|
| 78 |
-
isQuiz: f.isQuiz
|
| 79 |
-
}))
|
| 80 |
-
});
|
| 81 |
-
console.log('β
File retrieval:', getData.success ? 'SUCCESS' : 'FAILED');
|
| 82 |
-
|
| 83 |
-
if (getData.success && getData.files) {
|
| 84 |
-
console.log('\nπ Files found:');
|
| 85 |
-
getData.files.forEach(file => {
|
| 86 |
-
console.log(` - ${file.name} (${file.size} bytes)${file.isQuiz ? ' [QUIZ]' : ''}`);
|
| 87 |
-
if (file.name === 'quiz.json' && file.content) {
|
| 88 |
-
console.log(' Quiz content preview:', file.content.substring(0, 100) + '...');
|
| 89 |
-
}
|
| 90 |
-
});
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
// Test 4: Save to public folder
|
| 94 |
-
console.log('\nπ’ Test 4: Saving to public folder...');
|
| 95 |
-
const publicResponse = await fetch(`${API_URL}/api/mcp-handler`, {
|
| 96 |
-
method: 'POST',
|
| 97 |
-
headers: { 'Content-Type': 'application/json' },
|
| 98 |
-
body: JSON.stringify({
|
| 99 |
-
sessionId: sessionId,
|
| 100 |
-
action: 'save_public',
|
| 101 |
-
fileName: 'public_test.txt',
|
| 102 |
-
content: 'This is a public file'
|
| 103 |
-
})
|
| 104 |
-
});
|
| 105 |
-
|
| 106 |
-
const publicData = await publicResponse.json();
|
| 107 |
-
console.log('Public save response:', publicData);
|
| 108 |
-
console.log('β
Public save:', publicData.success ? 'SUCCESS' : 'FAILED');
|
| 109 |
-
|
| 110 |
-
// Summary
|
| 111 |
-
console.log('\n' + '='.repeat(50));
|
| 112 |
-
console.log('π TEST SUMMARY:');
|
| 113 |
-
console.log(` Session ID: ${sessionId}`);
|
| 114 |
-
console.log(` Files in session: ${getData.count || 0}`);
|
| 115 |
-
console.log(` Quiz detected: ${getData.files?.some(f => f.isQuiz) ? 'YES' : 'NO'}`);
|
| 116 |
-
console.log('='.repeat(50));
|
| 117 |
-
|
| 118 |
-
} catch (error) {
|
| 119 |
-
console.error('β Test failed with error:', error);
|
| 120 |
-
console.error('\nMake sure:');
|
| 121 |
-
console.error('1. Your Next.js server is running (npm run dev)');
|
| 122 |
-
console.error('2. The API endpoint is accessible');
|
| 123 |
-
console.error('3. The /tmp directory is writable');
|
| 124 |
-
}
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
// Run the test
|
| 128 |
-
testAPI();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-download.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
import fs from 'fs';
|
| 2 |
-
import path from 'path';
|
| 3 |
-
|
| 4 |
-
async function testDownload(url, expectedSize) {
|
| 5 |
-
try {
|
| 6 |
-
console.log(`Testing: ${url}`);
|
| 7 |
-
const response = await fetch(url);
|
| 8 |
-
|
| 9 |
-
if (!response.ok) {
|
| 10 |
-
const text = await response.text();
|
| 11 |
-
console.log(` β Failed: ${response.status} - ${text}`);
|
| 12 |
-
return false;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
const buffer = await response.arrayBuffer();
|
| 16 |
-
const contentType = response.headers.get('content-type');
|
| 17 |
-
const contentDisposition = response.headers.get('content-disposition');
|
| 18 |
-
|
| 19 |
-
console.log(` β Status: ${response.status}`);
|
| 20 |
-
console.log(` β Content-Type: ${contentType}`);
|
| 21 |
-
console.log(` β Content-Disposition: ${contentDisposition}`);
|
| 22 |
-
console.log(` β Size: ${buffer.byteLength} bytes`);
|
| 23 |
-
|
| 24 |
-
if (expectedSize && buffer.byteLength !== expectedSize) {
|
| 25 |
-
console.log(` β Warning: Expected ${expectedSize} bytes but got ${buffer.byteLength}`);
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
return true;
|
| 29 |
-
} catch (error) {
|
| 30 |
-
console.log(` β Error: ${error.message}`);
|
| 31 |
-
return false;
|
| 32 |
-
}
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
async function runTests() {
|
| 36 |
-
console.log('π§ͺ Testing Reuben OS File Download Functionality\n');
|
| 37 |
-
|
| 38 |
-
const baseUrl = 'http://localhost:3000';
|
| 39 |
-
|
| 40 |
-
// Wait a moment for server to be fully ready
|
| 41 |
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 42 |
-
|
| 43 |
-
console.log('1. Testing root-level file download (public):');
|
| 44 |
-
await testDownload(`${baseUrl}/api/sessions/download?file=test-download.txt&public=true`);
|
| 45 |
-
|
| 46 |
-
console.log('\n2. Testing nested file download (public):');
|
| 47 |
-
await testDownload(`${baseUrl}/api/sessions/download?file=test-folder/nested-file.pdf&public=true`);
|
| 48 |
-
|
| 49 |
-
console.log('\n3. Testing with URL encoding (public):');
|
| 50 |
-
await testDownload(`${baseUrl}/api/sessions/download?file=${encodeURIComponent('test-folder/nested-file.pdf')}&public=true`);
|
| 51 |
-
|
| 52 |
-
console.log('\n4. Testing error case - non-existent file:');
|
| 53 |
-
await testDownload(`${baseUrl}/api/sessions/download?file=non-existent.txt&public=true`);
|
| 54 |
-
|
| 55 |
-
console.log('\n5. Testing error case - missing filename:');
|
| 56 |
-
await testDownload(`${baseUrl}/api/sessions/download?public=true`);
|
| 57 |
-
|
| 58 |
-
console.log('\nβ
Download functionality tests completed!');
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
runTests().catch(console.error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-mcp-changes.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env node
|
| 2 |
-
// Test script for MCP server changes
|
| 3 |
-
|
| 4 |
-
import fetch from 'node-fetch';
|
| 5 |
-
|
| 6 |
-
const BASE_URL = process.env.REUBENOS_URL || 'http://localhost:3000';
|
| 7 |
-
const API_ENDPOINT = `${BASE_URL}/api/mcp-handler`;
|
| 8 |
-
|
| 9 |
-
async function testFlutterCodeSave() {
|
| 10 |
-
console.log('Testing Flutter code save...');
|
| 11 |
-
|
| 12 |
-
const flutterCode = `import 'package:flutter/material.dart';
|
| 13 |
-
|
| 14 |
-
void main() {
|
| 15 |
-
runApp(MyApp());
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
class MyApp extends StatelessWidget {
|
| 19 |
-
@override
|
| 20 |
-
Widget build(BuildContext context) {
|
| 21 |
-
return MaterialApp(
|
| 22 |
-
title: 'Flutter Demo',
|
| 23 |
-
theme: ThemeData(
|
| 24 |
-
primarySwatch: Colors.blue,
|
| 25 |
-
),
|
| 26 |
-
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
| 27 |
-
);
|
| 28 |
-
}
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
class MyHomePage extends StatefulWidget {
|
| 32 |
-
MyHomePage({Key? key, required this.title}) : super(key: key);
|
| 33 |
-
final String title;
|
| 34 |
-
|
| 35 |
-
@override
|
| 36 |
-
_MyHomePageState createState() => _MyHomePageState();
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
class _MyHomePageState extends State<MyHomePage> {
|
| 40 |
-
int _counter = 0;
|
| 41 |
-
|
| 42 |
-
void _incrementCounter() {
|
| 43 |
-
setState(() {
|
| 44 |
-
_counter++;
|
| 45 |
-
});
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
@override
|
| 49 |
-
Widget build(BuildContext context) {
|
| 50 |
-
return Scaffold(
|
| 51 |
-
appBar: AppBar(
|
| 52 |
-
title: Text(widget.title),
|
| 53 |
-
),
|
| 54 |
-
body: Center(
|
| 55 |
-
child: Column(
|
| 56 |
-
mainAxisAlignment: MainAxisAlignment.center,
|
| 57 |
-
children: <Widget>[
|
| 58 |
-
Text(
|
| 59 |
-
'You have pushed the button this many times:',
|
| 60 |
-
),
|
| 61 |
-
Text(
|
| 62 |
-
'$_counter',
|
| 63 |
-
style: Theme.of(context).textTheme.headline4,
|
| 64 |
-
),
|
| 65 |
-
],
|
| 66 |
-
),
|
| 67 |
-
),
|
| 68 |
-
floatingActionButton: FloatingActionButton(
|
| 69 |
-
onPressed: _incrementCounter,
|
| 70 |
-
tooltip: 'Increment',
|
| 71 |
-
child: Icon(Icons.add),
|
| 72 |
-
),
|
| 73 |
-
);
|
| 74 |
-
}
|
| 75 |
-
}`;
|
| 76 |
-
|
| 77 |
-
try {
|
| 78 |
-
const response = await fetch(API_ENDPOINT, {
|
| 79 |
-
method: 'POST',
|
| 80 |
-
headers: { 'Content-Type': 'application/json' },
|
| 81 |
-
body: JSON.stringify({
|
| 82 |
-
passkey: 'test123',
|
| 83 |
-
action: 'save_file',
|
| 84 |
-
fileName: 'main.dart',
|
| 85 |
-
content: flutterCode,
|
| 86 |
-
isPublic: false,
|
| 87 |
-
}),
|
| 88 |
-
});
|
| 89 |
-
|
| 90 |
-
const data = await response.json();
|
| 91 |
-
console.log('Flutter code save result:', data);
|
| 92 |
-
|
| 93 |
-
if (data.success) {
|
| 94 |
-
console.log('β
Flutter code saved successfully!');
|
| 95 |
-
|
| 96 |
-
// Try to read it back
|
| 97 |
-
const readUrl = new URL(API_ENDPOINT);
|
| 98 |
-
readUrl.searchParams.set('passkey', 'test123');
|
| 99 |
-
|
| 100 |
-
const readResponse = await fetch(readUrl, {
|
| 101 |
-
method: 'GET',
|
| 102 |
-
headers: { 'Content-Type': 'application/json' },
|
| 103 |
-
});
|
| 104 |
-
|
| 105 |
-
const readData = await readResponse.json();
|
| 106 |
-
if (readData.success) {
|
| 107 |
-
const file = readData.files.find(f => f.name === 'main.dart');
|
| 108 |
-
if (file && file.content === flutterCode) {
|
| 109 |
-
console.log('β
Flutter code verified - content matches!');
|
| 110 |
-
} else {
|
| 111 |
-
console.log('β οΈ Flutter code content mismatch or file not found');
|
| 112 |
-
}
|
| 113 |
-
}
|
| 114 |
-
} else {
|
| 115 |
-
console.log('β Failed to save Flutter code:', data.error);
|
| 116 |
-
}
|
| 117 |
-
} catch (error) {
|
| 118 |
-
console.error('Error testing Flutter code save:', error);
|
| 119 |
-
}
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
async function testAnalyzeQuiz() {
|
| 123 |
-
console.log('\nTesting analyze quiz functionality...');
|
| 124 |
-
|
| 125 |
-
// First, create a sample quiz
|
| 126 |
-
const quiz = {
|
| 127 |
-
title: "Sample Quiz",
|
| 128 |
-
description: "Test quiz for analysis",
|
| 129 |
-
questions: [
|
| 130 |
-
{
|
| 131 |
-
id: "q1",
|
| 132 |
-
type: "multiple_choice",
|
| 133 |
-
question: "What is 2+2?",
|
| 134 |
-
options: ["3", "4", "5", "6"],
|
| 135 |
-
correctAnswer: "4",
|
| 136 |
-
points: 1
|
| 137 |
-
},
|
| 138 |
-
{
|
| 139 |
-
id: "q2",
|
| 140 |
-
type: "multiple_choice",
|
| 141 |
-
question: "What is the capital of France?",
|
| 142 |
-
options: ["London", "Berlin", "Paris", "Madrid"],
|
| 143 |
-
correctAnswer: "Paris",
|
| 144 |
-
points: 1
|
| 145 |
-
},
|
| 146 |
-
{
|
| 147 |
-
id: "q3",
|
| 148 |
-
type: "true_false",
|
| 149 |
-
question: "The Earth is flat",
|
| 150 |
-
correctAnswer: false,
|
| 151 |
-
points: 1
|
| 152 |
-
}
|
| 153 |
-
]
|
| 154 |
-
};
|
| 155 |
-
|
| 156 |
-
const quizAnswers = {
|
| 157 |
-
answers: {
|
| 158 |
-
"q1": "4",
|
| 159 |
-
"q2": "London",
|
| 160 |
-
"q3": false
|
| 161 |
-
}
|
| 162 |
-
};
|
| 163 |
-
|
| 164 |
-
try {
|
| 165 |
-
// Save quiz.json
|
| 166 |
-
await fetch(API_ENDPOINT, {
|
| 167 |
-
method: 'POST',
|
| 168 |
-
headers: { 'Content-Type': 'application/json' },
|
| 169 |
-
body: JSON.stringify({
|
| 170 |
-
passkey: 'test123',
|
| 171 |
-
action: 'save_file',
|
| 172 |
-
fileName: 'quiz.json',
|
| 173 |
-
content: JSON.stringify(quiz, null, 2),
|
| 174 |
-
isPublic: false,
|
| 175 |
-
}),
|
| 176 |
-
});
|
| 177 |
-
|
| 178 |
-
// Save quiz_answers.json
|
| 179 |
-
await fetch(API_ENDPOINT, {
|
| 180 |
-
method: 'POST',
|
| 181 |
-
headers: { 'Content-Type': 'application/json' },
|
| 182 |
-
body: JSON.stringify({
|
| 183 |
-
passkey: 'test123',
|
| 184 |
-
action: 'save_file',
|
| 185 |
-
fileName: 'quiz_answers.json',
|
| 186 |
-
content: JSON.stringify(quizAnswers, null, 2),
|
| 187 |
-
isPublic: false,
|
| 188 |
-
}),
|
| 189 |
-
});
|
| 190 |
-
|
| 191 |
-
console.log('β
Quiz and answers saved. Ready to test analyze function.');
|
| 192 |
-
console.log('Note: The analyze_quiz function needs to be called through the MCP server.');
|
| 193 |
-
console.log('Expected results: 2/3 correct (q1 β
, q2 β, q3 β
)');
|
| 194 |
-
} catch (error) {
|
| 195 |
-
console.error('Error setting up quiz test:', error);
|
| 196 |
-
}
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
// Run tests
|
| 200 |
-
console.log('Starting MCP server tests...');
|
| 201 |
-
console.log('Using server:', BASE_URL);
|
| 202 |
-
|
| 203 |
-
testFlutterCodeSave().then(() => {
|
| 204 |
-
return testAnalyzeQuiz();
|
| 205 |
-
}).then(() => {
|
| 206 |
-
console.log('\nβ
All tests completed!');
|
| 207 |
-
console.log('Note: To fully test the analyze_quiz function, use the MCP client with the analyze_quiz tool.');
|
| 208 |
-
}).catch(error => {
|
| 209 |
-
console.error('Test failed:', error);
|
| 210 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-mcp-endpoint.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
| 1 |
-
// Test script for MCP handler API
|
| 2 |
-
// Run with: node test-mcp-endpoint.js
|
| 3 |
-
|
| 4 |
-
const BASE_URL = 'https://mcp-1st-birthday-reuben-os.hf.space';
|
| 5 |
-
const API_URL = `${BASE_URL}/api/mcp-handler`;
|
| 6 |
-
|
| 7 |
-
async function testMCPEndpoint() {
|
| 8 |
-
console.log('π§ͺ Testing MCP Handler API on Hugging Face...\n');
|
| 9 |
-
|
| 10 |
-
// Test 1: GET without passkey (should fail with error)
|
| 11 |
-
console.log('Test 1: GET without passkey');
|
| 12 |
-
try {
|
| 13 |
-
const response = await fetch(API_URL);
|
| 14 |
-
const data = await response.json();
|
| 15 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 16 |
-
console.log('Expected error message: β\n');
|
| 17 |
-
} catch (error) {
|
| 18 |
-
console.log('β Error:', error.message, '\n');
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
// Test 2: POST - Save a test file to public folder
|
| 22 |
-
console.log('Test 2: POST - Save public file');
|
| 23 |
-
try {
|
| 24 |
-
const response = await fetch(API_URL, {
|
| 25 |
-
method: 'POST',
|
| 26 |
-
headers: { 'Content-Type': 'application/json' },
|
| 27 |
-
body: JSON.stringify({
|
| 28 |
-
action: 'save_file',
|
| 29 |
-
fileName: 'test.txt',
|
| 30 |
-
content: 'Hello from MCP test!',
|
| 31 |
-
isPublic: true,
|
| 32 |
-
}),
|
| 33 |
-
});
|
| 34 |
-
const data = await response.json();
|
| 35 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 36 |
-
if (data.success) {
|
| 37 |
-
console.log('β File saved successfully to public folder!\n');
|
| 38 |
-
} else {
|
| 39 |
-
console.log('β Save failed:', data.error, '\n');
|
| 40 |
-
}
|
| 41 |
-
} catch (error) {
|
| 42 |
-
console.log('β Error:', error.message, '\n');
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
// Test 3: GET - List public files
|
| 46 |
-
console.log('Test 3: GET - List public files');
|
| 47 |
-
try {
|
| 48 |
-
const url = new URL(API_URL);
|
| 49 |
-
url.searchParams.set('isPublic', 'true');
|
| 50 |
-
|
| 51 |
-
const response = await fetch(url);
|
| 52 |
-
const data = await response.json();
|
| 53 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 54 |
-
if (data.success) {
|
| 55 |
-
console.log(`β Found ${data.count} public file(s)\n`);
|
| 56 |
-
}
|
| 57 |
-
} catch (error) {
|
| 58 |
-
console.log('β Error:', error.message, '\n');
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
// Test 4: POST - Save with passkey
|
| 62 |
-
console.log('Test 4: POST - Save with passkey');
|
| 63 |
-
const testPasskey = 'test-passkey-123';
|
| 64 |
-
try {
|
| 65 |
-
const response = await fetch(API_URL, {
|
| 66 |
-
method: 'POST',
|
| 67 |
-
headers: { 'Content-Type': 'application/json' },
|
| 68 |
-
body: JSON.stringify({
|
| 69 |
-
passkey: testPasskey,
|
| 70 |
-
action: 'save_file',
|
| 71 |
-
fileName: 'secure_test.dart',
|
| 72 |
-
content: `// Flutter test file\nvoid main() {\n print('Hello from secure storage!');\n}`,
|
| 73 |
-
isPublic: false,
|
| 74 |
-
}),
|
| 75 |
-
});
|
| 76 |
-
const data = await response.json();
|
| 77 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 78 |
-
if (data.success) {
|
| 79 |
-
console.log('β File saved successfully to secure storage!\n');
|
| 80 |
-
}
|
| 81 |
-
} catch (error) {
|
| 82 |
-
console.log('β Error:', error.message, '\n');
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
// Test 5: GET - List files with passkey
|
| 86 |
-
console.log('Test 5: GET - List files with passkey');
|
| 87 |
-
try {
|
| 88 |
-
const url = new URL(API_URL);
|
| 89 |
-
url.searchParams.set('passkey', testPasskey);
|
| 90 |
-
|
| 91 |
-
const response = await fetch(url);
|
| 92 |
-
const data = await response.json();
|
| 93 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 94 |
-
if (data.success) {
|
| 95 |
-
console.log(`β Found ${data.count} file(s) for passkey: ${testPasskey}\n`);
|
| 96 |
-
}
|
| 97 |
-
} catch (error) {
|
| 98 |
-
console.log('β Error:', error.message, '\n');
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
// Test 6: Deploy a test quiz
|
| 102 |
-
console.log('Test 6: POST - Deploy quiz');
|
| 103 |
-
try {
|
| 104 |
-
const quizData = {
|
| 105 |
-
title: 'Test Quiz',
|
| 106 |
-
description: 'A test quiz from MCP',
|
| 107 |
-
questions: [
|
| 108 |
-
{
|
| 109 |
-
id: 'q1',
|
| 110 |
-
question: 'What is 2+2?',
|
| 111 |
-
type: 'multiple-choice',
|
| 112 |
-
options: ['3', '4', '5', '6'],
|
| 113 |
-
correctAnswer: 1,
|
| 114 |
-
points: 1,
|
| 115 |
-
},
|
| 116 |
-
],
|
| 117 |
-
};
|
| 118 |
-
|
| 119 |
-
const response = await fetch(API_URL, {
|
| 120 |
-
method: 'POST',
|
| 121 |
-
headers: { 'Content-Type': 'application/json' },
|
| 122 |
-
body: JSON.stringify({
|
| 123 |
-
passkey: testPasskey,
|
| 124 |
-
action: 'deploy_quiz',
|
| 125 |
-
fileName: 'quiz.json',
|
| 126 |
-
content: JSON.stringify(quizData, null, 2),
|
| 127 |
-
isPublic: false,
|
| 128 |
-
}),
|
| 129 |
-
});
|
| 130 |
-
const data = await response.json();
|
| 131 |
-
console.log('β
Response:', JSON.stringify(data, null, 2));
|
| 132 |
-
if (data.success) {
|
| 133 |
-
console.log('β Quiz deployed successfully!\n');
|
| 134 |
-
}
|
| 135 |
-
} catch (error) {
|
| 136 |
-
console.log('β Error:', error.message, '\n');
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
console.log('π All tests completed!\n');
|
| 140 |
-
console.log('Summary:');
|
| 141 |
-
console.log('- MCP Handler API is accessible');
|
| 142 |
-
console.log('- Public file uploads work');
|
| 143 |
-
console.log('- Passkey-protected uploads work');
|
| 144 |
-
console.log('- File listing works');
|
| 145 |
-
console.log('- Quiz deployment works');
|
| 146 |
-
console.log('\nβ
Claude Desktop can now upload files to ReubenOS!');
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
// Run tests
|
| 150 |
-
testMCPEndpoint().catch(console.error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-mcp-local.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 1 |
-
// test-mcp-local.js - Test MCP server locally
|
| 2 |
-
// This simulates what Claude would do
|
| 3 |
-
|
| 4 |
-
import { spawn } from 'child_process';
|
| 5 |
-
import readline from 'readline';
|
| 6 |
-
|
| 7 |
-
console.log('π Starting MCP Server Test...\n');
|
| 8 |
-
|
| 9 |
-
// Start the MCP server
|
| 10 |
-
const mcp = spawn('node', ['mcp-server.js'], {
|
| 11 |
-
env: {
|
| 12 |
-
...process.env,
|
| 13 |
-
REUBENOS_URL: 'https://huggingface.co/spaces/MCP-1st-Birthday/Reuben_OS'
|
| 14 |
-
}
|
| 15 |
-
});
|
| 16 |
-
|
| 17 |
-
const rl = readline.createInterface({
|
| 18 |
-
input: process.stdin,
|
| 19 |
-
output: process.stdout
|
| 20 |
-
});
|
| 21 |
-
|
| 22 |
-
// Handle MCP output
|
| 23 |
-
mcp.stderr.on('data', (data) => {
|
| 24 |
-
console.log('MCP:', data.toString());
|
| 25 |
-
});
|
| 26 |
-
|
| 27 |
-
mcp.stdout.on('data', (data) => {
|
| 28 |
-
console.log('MCP Output:', data.toString());
|
| 29 |
-
});
|
| 30 |
-
|
| 31 |
-
// Send test commands
|
| 32 |
-
async function testCommands() {
|
| 33 |
-
const sessionId = 'session_1763722877048_527d6bb8b7473568';
|
| 34 |
-
|
| 35 |
-
// Test manage_files tool
|
| 36 |
-
const testFileCommand = {
|
| 37 |
-
jsonrpc: '2.0',
|
| 38 |
-
id: 1,
|
| 39 |
-
method: 'tools/call',
|
| 40 |
-
params: {
|
| 41 |
-
name: 'manage_files',
|
| 42 |
-
arguments: {
|
| 43 |
-
sessionId: sessionId,
|
| 44 |
-
action: 'save',
|
| 45 |
-
fileName: 'test_from_mcp.txt',
|
| 46 |
-
content: 'This is a test from the MCP server'
|
| 47 |
-
}
|
| 48 |
-
}
|
| 49 |
-
};
|
| 50 |
-
|
| 51 |
-
console.log('π Testing file save...');
|
| 52 |
-
console.log('Command:', JSON.stringify(testFileCommand, null, 2));
|
| 53 |
-
|
| 54 |
-
// Note: This would need proper JSON-RPC communication
|
| 55 |
-
// For now, this just shows what would be sent
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
console.log('π Session ID for testing:', 'session_1763722877048_527d6bb8b7473568');
|
| 59 |
-
console.log('\nTo test manually:');
|
| 60 |
-
console.log('1. Restart Claude Desktop');
|
| 61 |
-
console.log('2. Tell Claude: "My session is session_1763722877048_527d6bb8b7473568"');
|
| 62 |
-
console.log('3. Ask Claude to save a file or deploy a quiz\n');
|
| 63 |
-
|
| 64 |
-
rl.question('Press Enter to exit...', () => {
|
| 65 |
-
mcp.kill();
|
| 66 |
-
rl.close();
|
| 67 |
-
});
|
| 68 |
-
|
| 69 |
-
testCommands();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-voice-studio.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env node
|
| 2 |
-
/**
|
| 3 |
-
* Voice Studio Test Script
|
| 4 |
-
* Tests the Voice Studio MCP tools without needing Claude Desktop
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
const BASE_URL = process.env.REUBENOS_URL || 'http://localhost:3000';
|
| 8 |
-
|
| 9 |
-
async function testSongGeneration() {
|
| 10 |
-
console.log('π΅ Testing Song Generation...\n');
|
| 11 |
-
|
| 12 |
-
const songData = {
|
| 13 |
-
title: 'Test Song',
|
| 14 |
-
style: 'pop',
|
| 15 |
-
lyrics: 'This is a test song\nWith simple lyrics\nTo verify the API works',
|
| 16 |
-
passkey: 'test123'
|
| 17 |
-
};
|
| 18 |
-
|
| 19 |
-
try {
|
| 20 |
-
const response = await fetch(`${BASE_URL}/api/voice/generate-song`, {
|
| 21 |
-
method: 'POST',
|
| 22 |
-
headers: { 'Content-Type': 'application/json' },
|
| 23 |
-
body: JSON.stringify(songData),
|
| 24 |
-
});
|
| 25 |
-
|
| 26 |
-
const data = await response.json();
|
| 27 |
-
|
| 28 |
-
if (response.ok && data.success) {
|
| 29 |
-
console.log('β
Song generation endpoint works!');
|
| 30 |
-
console.log(` Title: ${data.content.title}`);
|
| 31 |
-
console.log(` Style: ${data.content.style}`);
|
| 32 |
-
console.log(` Audio size: ${data.content.audioUrl.length} characters`);
|
| 33 |
-
} else {
|
| 34 |
-
console.error('β Song generation failed:', data.error);
|
| 35 |
-
}
|
| 36 |
-
} catch (error) {
|
| 37 |
-
console.error('β Error:', error.message);
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
console.log('');
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
async function testStoryGeneration() {
|
| 44 |
-
console.log('π Testing Story Generation...\n');
|
| 45 |
-
|
| 46 |
-
const storyData = {
|
| 47 |
-
title: 'Test Story',
|
| 48 |
-
content: 'Once upon a time, there was a test story. It was short and simple, designed to verify that the API works correctly.',
|
| 49 |
-
passkey: 'test123'
|
| 50 |
-
};
|
| 51 |
-
|
| 52 |
-
try {
|
| 53 |
-
const response = await fetch(`${BASE_URL}/api/voice/generate-story`, {
|
| 54 |
-
method: 'POST',
|
| 55 |
-
headers: { 'Content-Type': 'application/json' },
|
| 56 |
-
body: JSON.stringify(storyData),
|
| 57 |
-
});
|
| 58 |
-
|
| 59 |
-
const data = await response.json();
|
| 60 |
-
|
| 61 |
-
if (response.ok && data.success) {
|
| 62 |
-
console.log('β
Story generation endpoint works!');
|
| 63 |
-
console.log(` Title: ${data.content.title}`);
|
| 64 |
-
console.log(` Content length: ${data.content.storyContent.length} chars`);
|
| 65 |
-
console.log(` Audio size: ${data.content.audioUrl.length} characters`);
|
| 66 |
-
} else {
|
| 67 |
-
console.error('β Story generation failed:', data.error);
|
| 68 |
-
}
|
| 69 |
-
} catch (error) {
|
| 70 |
-
console.error('β Error:', error.message);
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
console.log('');
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
async function testContentRetrieval() {
|
| 77 |
-
console.log('πΎ Testing Content Retrieval...\n');
|
| 78 |
-
|
| 79 |
-
try {
|
| 80 |
-
const response = await fetch(`${BASE_URL}/api/voice/save?passkey=test123`);
|
| 81 |
-
const data = await response.json();
|
| 82 |
-
|
| 83 |
-
if (response.ok && data.success) {
|
| 84 |
-
console.log('β
Content retrieval works!');
|
| 85 |
-
console.log(` Found ${data.content.length} items`);
|
| 86 |
-
data.content.forEach((item, i) => {
|
| 87 |
-
console.log(` ${i + 1}. ${item.type}: ${item.title}`);
|
| 88 |
-
});
|
| 89 |
-
} else {
|
| 90 |
-
console.error('β Content retrieval failed:', data.error);
|
| 91 |
-
}
|
| 92 |
-
} catch (error) {
|
| 93 |
-
console.error('β Error:', error.message);
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
console.log('');
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
async function checkEnvironment() {
|
| 100 |
-
console.log('π§ Checking Environment...\n');
|
| 101 |
-
|
| 102 |
-
// Check if ELEVENLABS_API_KEY is set
|
| 103 |
-
if (process.env.ELEVENLABS_API_KEY) {
|
| 104 |
-
console.log('β
ELEVENLABS_API_KEY is set');
|
| 105 |
-
console.log(` Length: ${process.env.ELEVENLABS_API_KEY.length} characters`);
|
| 106 |
-
} else {
|
| 107 |
-
console.log('β ELEVENLABS_API_KEY is NOT set');
|
| 108 |
-
console.log(' Set it with: export ELEVENLABS_API_KEY="your_key"');
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
console.log('');
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
async function runAllTests() {
|
| 115 |
-
console.log('\nββββββββββββββββββββββββββββββββββββββββ');
|
| 116 |
-
console.log('β Voice Studio Test Suite β');
|
| 117 |
-
console.log('ββββββββββββββββββββββββββββββββββββββββ\n');
|
| 118 |
-
|
| 119 |
-
await checkEnvironment();
|
| 120 |
-
|
| 121 |
-
console.log('β οΈ NOTE: The following tests will only work if:');
|
| 122 |
-
console.log(' 1. ELEVENLABS_API_KEY is set');
|
| 123 |
-
console.log(' 2. Next.js dev server is running');
|
| 124 |
-
console.log(' 3. You have ElevenLabs API credits\n');
|
| 125 |
-
|
| 126 |
-
const readline = require('readline').createInterface({
|
| 127 |
-
input: process.stdin,
|
| 128 |
-
output: process.stdout
|
| 129 |
-
});
|
| 130 |
-
|
| 131 |
-
readline.question('Continue with API tests? (y/n) ', async (answer) => {
|
| 132 |
-
if (answer.toLowerCase() === 'y') {
|
| 133 |
-
await testSongGeneration();
|
| 134 |
-
await testStoryGeneration();
|
| 135 |
-
await testContentRetrieval();
|
| 136 |
-
|
| 137 |
-
console.log('ββββββββββββββββββββββββββββββββββββββββ');
|
| 138 |
-
console.log('β Tests Complete! β');
|
| 139 |
-
console.log('ββββββββββοΏ½οΏ½βββββββββββββββββββββββββββββ\n');
|
| 140 |
-
}
|
| 141 |
-
readline.close();
|
| 142 |
-
});
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
runAllTests();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|