cyrus-studio 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -65
- package/bin/mcp-server.js +394 -0
- package/dist/index.cjs +1167 -0
- package/dist/index.d.ts +351 -0
- package/dist/index.js +41 -0
- package/dist/index.mjs +1137 -0
- package/dist/protocol.d.ts +35 -0
- package/dist/protocol.js +31 -0
- package/dist/src/index.d.ts +1176 -0
- package/dist/src/index.js +41 -0
- package/package.json +39 -50
- package/dist/assets/AuthDialog-bjTyuBr7.js +0 -1
- package/dist/assets/CanvaSaveModal-BfM3xRxS.js +0 -1
- package/dist/assets/CodingWorkspace-BKcqDIZx.js +0 -6
- package/dist/assets/CreateAppDialog-D8utCQ_b.js +0 -1236
- package/dist/assets/CreateProjectDialog-BVn3XyiE.js +0 -1
- package/dist/assets/HistoryTimelineDialog-B6LM34kT.js +0 -1
- package/dist/assets/ImportModal-B5pjYSjk.js +0 -83
- package/dist/assets/KeyboardShortcutsDialog-azy2kfng.js +0 -1
- package/dist/assets/LiveRuntimeWorkspace-CFPr1I0-.js +0 -1
- package/dist/assets/ShareExportDialog-BrQois88.js +0 -1
- package/dist/assets/UserProfileDialog-B2TENVlq.js +0 -1
- package/dist/assets/WelcomeOnboardingModal-Dyig1GK1.js +0 -1
- package/dist/assets/index-Cc_706Lp.css +0 -2
- package/dist/assets/index-DM7S-Aaa.js +0 -793
- package/dist/assets/reactTsGenerator-CpUD2_5K.js +0 -1330
- package/dist/assets/rolldown-runtime-Dd_uD5pT.js +0 -1
- package/dist/assets/themes-Iute1uX4.js +0 -1
- package/dist/assets/typechecker-BbGTunMD.js +0 -2
- package/dist/assets/vendor-firebase-DNMzpOGz.js +0 -32
- package/dist/assets/vendor-icons-CT-OIlA-.js +0 -13
- package/dist/assets/vendor-jszip-BhaZ3FnA.js +0 -2
- package/dist/assets/vendor-react-DVnpuVEx.js +0 -227
- package/dist/assets/vendor-utils-BqqQ8OQf.js +0 -1
- package/dist/favicon.svg +0 -1
- package/dist/icons.svg +0 -24
- package/dist/index.html +0 -50
- package/dist/login-bg.png +0 -0
- package/dist/test-sdk-sandbox.html +0 -125
package/README.md
CHANGED
|
@@ -1,113 +1,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cyrus-studio
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://cyrustudio.vercel.app"><img src="https://img.shields.io/badge/Live_App-cyrustudio.vercel.app-6366f1?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo" /></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/v/cyrus-studio?style=for-the-badge&color=blue" alt="NPM Version" /></a>
|
|
6
6
|
<a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/dm/cyrus-studio?style=for-the-badge&color=green" alt="NPM Downloads" /></a>
|
|
7
|
-
<
|
|
7
|
+
<img src="https://img.shields.io/badge/License-MIT-amber?style=for-the-badge" alt="License" />
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
> **AI Visual Canvas Studio** — Embed Cyrus in any React app, generate full UIs from prompts, and export clean code for React, Flutter, SwiftUI & HTML.
|
|
11
|
+
|
|
12
|
+
🚀 **Try the Live App**: **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)**
|
|
13
|
+
|
|
10
14
|
---
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Install
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npm install cyrus-studio
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **Peer dependencies required**: `react >= 18` and `react-dom >= 18`
|
|
15
23
|
|
|
16
24
|
---
|
|
17
25
|
|
|
18
|
-
##
|
|
26
|
+
## Usage
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
- ⚡ **Multi-Framework Code Generation**:
|
|
22
|
-
- ⚛️ **React / Next.js** (Tailwind CSS, TypeScript)
|
|
23
|
-
- 📱 **Flutter / Dart** (Widget tree with theme binding)
|
|
24
|
-
- 🍎 **SwiftUI** (Native declarative iOS/macOS views)
|
|
25
|
-
- 🌐 **Clean HTML5 / CSS** (Semantic layout with Tailwind)
|
|
26
|
-
- 🤖 **AI Design & Code Copilot**: Integrate OpenAI, Google Gemini, Anthropic Claude, or local Ollama models to generate layouts, copy, and components via natural language.
|
|
27
|
-
- 👥 **Real-Time Multiplayer Collaboration**: Multiplayer room with live cursor tracking, conflict resolution, and commenting.
|
|
28
|
-
- 📐 **Responsive Viewport Controls**: Instant switching between Desktop (1440px), Tablet (768px), and Mobile (390px) viewports with auto-scaling.
|
|
29
|
-
- 🌗 **Design Systems & Theme Engine**: Global color palettes, typography tokens, component instances, and dynamic light/dark mode preview.
|
|
30
|
-
- 📦 **One-Click Export**: Download structured ZIP bundles, export individual page source files, or save high-resolution PNG/PDF artboards.
|
|
28
|
+
### 1. Embed the Cyrus Canvas in your React App
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
```tsx
|
|
31
|
+
import { CyrusCanvasIframe } from 'cyrus-studio';
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
export default function App() {
|
|
34
|
+
return (
|
|
35
|
+
<CyrusCanvasIframe
|
|
36
|
+
hostUrl="https://cyrustudio.vercel.app"
|
|
37
|
+
projectId="my-project"
|
|
38
|
+
theme="dark"
|
|
39
|
+
style={{ height: '800px' }}
|
|
40
|
+
onStateChange={(state) => console.log('Canvas state:', state)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
35
45
|
|
|
36
|
-
###
|
|
37
|
-
Open **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)** in your browser and start designing immediately.
|
|
46
|
+
### 2. Generate an Embed URL Programmatically
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
```ts
|
|
49
|
+
import { createCyrusEmbedUrl } from 'cyrus-studio';
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const url = createCyrusEmbedUrl({
|
|
52
|
+
hostUrl: 'https://cyrustudio.vercel.app',
|
|
53
|
+
projectId: 'landing-v2',
|
|
54
|
+
theme: 'light',
|
|
55
|
+
readOnly: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(url);
|
|
59
|
+
// https://cyrustudio.vercel.app/#/embed/landing-v2?theme=light&readOnly=true
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Use the Cyrus IR (Intermediate Representation) Protocol
|
|
63
|
+
|
|
64
|
+
The Cyrus IR is a universal JSON schema for AI-generated UIs. Use it to pass designs between your AI pipeline and the Cyrus canvas.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import {
|
|
68
|
+
validateAndRepairCyrusIR,
|
|
69
|
+
getCyrusLLMSystemPrompt,
|
|
70
|
+
type CyrusIRDocument,
|
|
71
|
+
} from 'cyrus-studio';
|
|
45
72
|
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
// Get the system prompt to paste into your LLM
|
|
74
|
+
const systemPrompt = getCyrusLLMSystemPrompt();
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
// Validate and repair raw LLM output
|
|
77
|
+
const rawAiOutput = `{ "version": "1.0", "projectTitle": "My App", "pages": [...] }`;
|
|
78
|
+
const doc: CyrusIRDocument = validateAndRepairCyrusIR(rawAiOutput);
|
|
79
|
+
|
|
80
|
+
console.log(doc.pages[0].elements);
|
|
51
81
|
```
|
|
52
82
|
|
|
53
|
-
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `CyrusCanvasIframe`
|
|
54
88
|
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
React component that embeds the Cyrus Visual Canvas as an iframe.
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `hostUrl` | `string` | `http://localhost:5173` | URL of the Cyrus app |
|
|
94
|
+
| `projectId` | `string` | `"demo"` | Project ID to load in canvas |
|
|
95
|
+
| `theme` | `"light" \| "dark"` | `"light"` | Canvas color theme |
|
|
96
|
+
| `onStateChange` | `(state: any) => void` | — | Callback fired when canvas state changes |
|
|
97
|
+
| `style` | `CSSProperties` | — | Inline styles for the iframe |
|
|
98
|
+
| `className` | `string` | — | CSS class for the iframe |
|
|
99
|
+
|
|
100
|
+
All standard `<iframe>` HTML attributes are also supported.
|
|
57
101
|
|
|
58
102
|
---
|
|
59
103
|
|
|
60
|
-
|
|
104
|
+
### `createCyrusEmbedUrl(options)`
|
|
61
105
|
|
|
62
|
-
|
|
106
|
+
Returns an embeddable URL string.
|
|
63
107
|
|
|
64
|
-
```
|
|
65
|
-
|
|
108
|
+
```ts
|
|
109
|
+
createCyrusEmbedUrl({
|
|
110
|
+
projectId?: string; // Project to load
|
|
111
|
+
hostUrl?: string; // Cyrus host (default: localhost:5173)
|
|
112
|
+
theme?: 'light' | 'dark';
|
|
113
|
+
readOnly?: boolean;
|
|
114
|
+
})
|
|
66
115
|
```
|
|
67
116
|
|
|
68
|
-
|
|
69
|
-
| :--- | :--- |
|
|
70
|
-
| `VITE_STRIPE_PUBLISHABLE_KEY` | Stripe Publishable Key for billing checkout. |
|
|
71
|
-
| `STRIPE_SECRET_KEY` | Stripe Secret Key for serverless subscription sessions. |
|
|
72
|
-
| `GEMINI_API_KEY` | Google Gemini API key for AI generation. |
|
|
73
|
-
| `OPENAI_API_KEY` | OpenAI API key for GPT-4o design assistant. |
|
|
74
|
-
| `ANTHROPIC_API_KEY` | Anthropic Claude API key. |
|
|
75
|
-
| `JWT_SECRET` | Secret string for production session token signing. |
|
|
117
|
+
---
|
|
76
118
|
|
|
77
|
-
|
|
119
|
+
### `validateAndRepairCyrusIR(rawInput)`
|
|
78
120
|
|
|
79
|
-
|
|
121
|
+
Parses, validates, and repairs raw AI output (string or object) into a valid `CyrusIRDocument`. Safe to call with any LLM response — never throws.
|
|
80
122
|
|
|
81
|
-
|
|
123
|
+
---
|
|
82
124
|
|
|
83
|
-
|
|
84
|
-
# Start local development server (Client + Server concurrently)
|
|
85
|
-
npm run dev
|
|
125
|
+
### `getCyrusLLMSystemPrompt()`
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
npm run typecheck
|
|
127
|
+
Returns the recommended system prompt string to paste into Claude, GPT-4, Gemini, or any other LLM so it outputs valid Cyrus IR JSON.
|
|
89
128
|
|
|
90
|
-
|
|
91
|
-
npm run test
|
|
129
|
+
---
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
npm run build
|
|
131
|
+
## TypeScript Types
|
|
95
132
|
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
```ts
|
|
134
|
+
import type {
|
|
135
|
+
CyrusIRDocument,
|
|
136
|
+
CyrusIRPage,
|
|
137
|
+
CyrusIRElement,
|
|
138
|
+
CyrusIframeProps,
|
|
139
|
+
} from 'cyrus-studio';
|
|
98
140
|
```
|
|
99
141
|
|
|
100
142
|
---
|
|
101
143
|
|
|
102
|
-
##
|
|
144
|
+
## CommonJS Support
|
|
103
145
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
146
|
+
```js
|
|
147
|
+
// Works with require() too
|
|
148
|
+
const { CyrusCanvasIframe, createCyrusEmbedUrl } = require('cyrus-studio');
|
|
149
|
+
```
|
|
108
150
|
|
|
109
151
|
---
|
|
110
152
|
|
|
111
|
-
##
|
|
153
|
+
## License
|
|
112
154
|
|
|
113
|
-
|
|
155
|
+
MIT © [Cyrus Studio Team](https://cyrustudio.vercel.app)
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cyrus Studio Model Context Protocol (MCP) CLI Entrypoint v1.0.8
|
|
5
|
+
*
|
|
6
|
+
* Standalone JSON-RPC 2.0 / MCP Server for:
|
|
7
|
+
* - Claude Desktop
|
|
8
|
+
* - Cursor
|
|
9
|
+
* - Antigravity / Gemini CLI
|
|
10
|
+
* - Autonomous AI Agents
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import readline from 'node:readline';
|
|
14
|
+
import { randomBytes } from 'node:crypto';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
|
|
19
|
+
function generateId(prefix = 'el') {
|
|
20
|
+
return `${prefix}_${randomBytes(5).toString('hex')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── LOCAL ATOMIC PERSISTENCE ───────────────────────────────────────
|
|
24
|
+
const DB_DIR = path.join(os.homedir(), '.cyrus-studio');
|
|
25
|
+
const DB_FILE = path.join(DB_DIR, 'db.json');
|
|
26
|
+
|
|
27
|
+
function loadMemoryDb() {
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.existsSync(DB_DIR)) fs.mkdirSync(DB_DIR, { recursive: true });
|
|
30
|
+
if (fs.existsSync(DB_FILE)) {
|
|
31
|
+
return JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
return { version: 1, projects: [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function saveMemoryDb(db) {
|
|
38
|
+
try {
|
|
39
|
+
if (!fs.existsSync(DB_DIR)) fs.mkdirSync(DB_DIR, { recursive: true });
|
|
40
|
+
fs.writeFileSync(DB_FILE, JSON.stringify(db, null, 2), 'utf8');
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── MCP TOOL SCHEMAS ───────────────────────────────────────────────
|
|
45
|
+
export const MCP_TOOLS = [
|
|
46
|
+
{
|
|
47
|
+
name: 'cyrus_generate_ui',
|
|
48
|
+
description: 'Generates a complete, structured visual UI page layout on the Cyrus Canvas from a text prompt. Creates the project, saves it, and returns the interactive canvas URLs and clean React code.',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
prompt: { type: 'string', description: 'Description of the UI to generate' },
|
|
53
|
+
type: { type: 'string', enum: ['webapp', 'website', 'dashboard', 'landing', 'mobile'], default: 'webapp' },
|
|
54
|
+
theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' },
|
|
55
|
+
preset: { type: 'string', enum: ['apple-hig', 'shadcn-zinc', 'shadcn-slate', 'shadcn-violet', 'shadcn-rose', 'tailwind-cyberpunk'], default: 'shadcn-zinc' }
|
|
56
|
+
},
|
|
57
|
+
required: ['prompt']
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'cyrus_create_element',
|
|
62
|
+
description: 'Creates and places a new high-fidelity UI widget/component onto the Cyrus Canvas (navbar, hero, button, pricingCard, metricCard, lineChart, searchBar, container, footer).',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
projectId: { type: 'string', description: 'Target project ID' },
|
|
67
|
+
type: { type: 'string', description: 'Component type' },
|
|
68
|
+
name: { type: 'string', description: 'Human-readable element label' },
|
|
69
|
+
x: { type: 'number' },
|
|
70
|
+
y: { type: 'number' },
|
|
71
|
+
width: { type: 'number' },
|
|
72
|
+
height: { type: 'number' },
|
|
73
|
+
styles: { type: 'object' },
|
|
74
|
+
content: { type: 'object' }
|
|
75
|
+
},
|
|
76
|
+
required: ['type']
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'cyrus_modify_element',
|
|
81
|
+
description: 'Updates specific visual styles, text content, or dimensions of an existing canvas element by ID.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
projectId: { type: 'string' },
|
|
86
|
+
elementId: { type: 'string' },
|
|
87
|
+
updates: { type: 'object' }
|
|
88
|
+
},
|
|
89
|
+
required: ['projectId', 'elementId', 'updates']
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'cyrus_delete_element',
|
|
94
|
+
description: 'Deletes an element from the Cyrus canvas by its element ID.',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
projectId: { type: 'string' },
|
|
99
|
+
elementId: { type: 'string' }
|
|
100
|
+
},
|
|
101
|
+
required: ['projectId', 'elementId']
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'cyrus_apply_design_system',
|
|
106
|
+
description: 'Applies global design system tokens (colors, typography, radii, theme presets) across the canvas.',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
projectId: { type: 'string' },
|
|
111
|
+
preset: { type: 'string', enum: ['apple-hig', 'shadcn-zinc', 'shadcn-slate', 'shadcn-violet', 'shadcn-rose', 'tailwind-cyberpunk'] },
|
|
112
|
+
colors: { type: 'object' },
|
|
113
|
+
radii: { type: 'object' }
|
|
114
|
+
},
|
|
115
|
+
required: ['projectId']
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'cyrus_get_canvas_document',
|
|
120
|
+
description: 'Retrieves the complete structured Cyrus IR JSON document of a canvas project.',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
projectId: { type: 'string' }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'cyrus_list_projects',
|
|
130
|
+
description: 'Lists all Cyrus Studio projects available with IDs, names, and preview URLs.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'cyrus_export_code',
|
|
138
|
+
description: 'Exports production-ready code (React + Tailwind CSS, Flutter, SwiftUI, or HTML + CSS) for the current visual canvas.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
projectId: { type: 'string' },
|
|
143
|
+
framework: { type: 'string', enum: ['react-tailwind', 'flutter', 'swiftui', 'html-css'], default: 'react-tailwind' }
|
|
144
|
+
},
|
|
145
|
+
required: ['projectId']
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// ─── PURE ESM CODE GENERATOR ─────────────────────────────────────────
|
|
151
|
+
function generateReactTailwindCode(page) {
|
|
152
|
+
const elements = page?.elements || [];
|
|
153
|
+
const componentName = (page?.name || 'Page').replace(/[^a-zA-Z0-9]/g, '') || 'CyrusApp';
|
|
154
|
+
|
|
155
|
+
const rendered = elements.map(el => {
|
|
156
|
+
const c = el.content || {};
|
|
157
|
+
return ` {/* ${el.name || el.type} */}
|
|
158
|
+
<div className="p-6 rounded-2xl border border-white/10 bg-neutral-900/60 shadow-xl">
|
|
159
|
+
<h3 className="text-xl font-bold text-white">${c.title || c.headline || c.text || el.name || 'Component'}</h3>
|
|
160
|
+
</div>`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return `import React from 'react';
|
|
164
|
+
|
|
165
|
+
export default function ${componentName}() {
|
|
166
|
+
return (
|
|
167
|
+
<div className="min-h-screen bg-[#09090b] text-neutral-100 p-8 space-y-6">
|
|
168
|
+
${rendered.join('\n\n')}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function handleToolCall(name, args = {}) {
|
|
175
|
+
const db = loadMemoryDb();
|
|
176
|
+
|
|
177
|
+
switch (name) {
|
|
178
|
+
case 'cyrus_generate_ui': {
|
|
179
|
+
const prompt = String(args.prompt || 'Modern Web Application').trim();
|
|
180
|
+
const theme = args.theme || 'dark';
|
|
181
|
+
const type = args.type || 'webapp';
|
|
182
|
+
const preset = args.preset || 'shadcn-zinc';
|
|
183
|
+
const projectId = `proj_${generateId()}`;
|
|
184
|
+
|
|
185
|
+
const elements = [
|
|
186
|
+
{
|
|
187
|
+
id: `el_${generateId()}`,
|
|
188
|
+
type: 'navbar',
|
|
189
|
+
name: 'Main Navigation Bar',
|
|
190
|
+
x: 40,
|
|
191
|
+
y: 30,
|
|
192
|
+
width: 1200,
|
|
193
|
+
height: 64,
|
|
194
|
+
styles: { backgroundColor: '#18181b', borderRadius: 12 },
|
|
195
|
+
content: { brandName: 'Cyrus Studio AI', buttonText: 'Launch App' }
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: `el_${generateId()}`,
|
|
199
|
+
type: 'hero',
|
|
200
|
+
name: 'Hero Section',
|
|
201
|
+
x: 40,
|
|
202
|
+
y: 110,
|
|
203
|
+
width: 1200,
|
|
204
|
+
height: 320,
|
|
205
|
+
styles: { backgroundColor: '#09090b', borderRadius: 16 },
|
|
206
|
+
content: {
|
|
207
|
+
badge: 'AI Generated Layout',
|
|
208
|
+
headline: prompt.slice(0, 60),
|
|
209
|
+
subtitle: `Generated for ${type.toUpperCase()} layout using ${preset} design tokens.`,
|
|
210
|
+
buttonText: 'Get Started'
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: `el_${generateId()}`,
|
|
215
|
+
type: 'pricingCard',
|
|
216
|
+
name: 'Subscription Plan',
|
|
217
|
+
x: 40,
|
|
218
|
+
y: 450,
|
|
219
|
+
width: 380,
|
|
220
|
+
height: 360,
|
|
221
|
+
styles: { backgroundColor: '#18181b', borderRadius: 16 },
|
|
222
|
+
content: {
|
|
223
|
+
badge: 'PRO TIER',
|
|
224
|
+
planName: 'Enterprise Engine',
|
|
225
|
+
price: '$49/mo',
|
|
226
|
+
features: ['Unlimited Canvas Projects', 'Real-time MCP Sync', 'React & Flutter Export']
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const page = { id: `page_${generateId()}`, name: 'Home', elements };
|
|
232
|
+
const projectDoc = {
|
|
233
|
+
id: projectId,
|
|
234
|
+
name: prompt.slice(0, 40),
|
|
235
|
+
version: '1.0.8',
|
|
236
|
+
theme,
|
|
237
|
+
type,
|
|
238
|
+
preset,
|
|
239
|
+
pages: [page],
|
|
240
|
+
createdAt: new Date().toISOString(),
|
|
241
|
+
updatedAt: new Date().toISOString()
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
db.projects.push({ document: projectDoc });
|
|
245
|
+
saveMemoryDb(db);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
projectId,
|
|
250
|
+
title: projectDoc.name,
|
|
251
|
+
elementCount: elements.length,
|
|
252
|
+
editorUrl: `https://cyrustudio.vercel.app/#/editor/${projectId}`,
|
|
253
|
+
previewUrl: `https://cyrustudio.vercel.app/#/embed/${projectId}?theme=${theme}`,
|
|
254
|
+
document: projectDoc,
|
|
255
|
+
code: generateReactTailwindCode(page)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
case 'cyrus_create_element': {
|
|
260
|
+
let targetProj = db.projects[db.projects.length - 1]?.document;
|
|
261
|
+
if (args.projectId) {
|
|
262
|
+
const found = db.projects.find(p => p.document?.id === args.projectId);
|
|
263
|
+
if (found) targetProj = found.document;
|
|
264
|
+
}
|
|
265
|
+
if (!targetProj) {
|
|
266
|
+
targetProj = {
|
|
267
|
+
id: `proj_${generateId()}`,
|
|
268
|
+
name: 'Cyrus Project',
|
|
269
|
+
pages: [{ id: `page_${generateId()}`, name: 'Home', elements: [] }]
|
|
270
|
+
};
|
|
271
|
+
db.projects.push({ document: targetProj });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const activePage = targetProj.pages[0];
|
|
275
|
+
const newEl = {
|
|
276
|
+
id: `el_${generateId()}`,
|
|
277
|
+
type: args.type || 'button',
|
|
278
|
+
name: args.name || args.type || 'New Element',
|
|
279
|
+
x: args.x || 100,
|
|
280
|
+
y: args.y || 120,
|
|
281
|
+
width: args.width || 320,
|
|
282
|
+
height: args.height || 120,
|
|
283
|
+
styles: args.styles || { backgroundColor: '#18181b', borderRadius: 12 },
|
|
284
|
+
content: args.content || { title: args.name || 'New Widget' }
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
activePage.elements.push(newEl);
|
|
288
|
+
saveMemoryDb(db);
|
|
289
|
+
|
|
290
|
+
return { success: true, projectId: targetProj.id, createdElement: newEl, totalElements: activePage.elements.length };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 'cyrus_modify_element': {
|
|
294
|
+
const { projectId, elementId, updates = {} } = args;
|
|
295
|
+
const proj = db.projects.find(p => p.document?.id === projectId);
|
|
296
|
+
if (!proj) throw new Error(`Project "${projectId}" not found.`);
|
|
297
|
+
|
|
298
|
+
let targetEl = null;
|
|
299
|
+
for (const page of (proj.document.pages || [])) {
|
|
300
|
+
targetEl = (page.elements || []).find(e => e.id === elementId);
|
|
301
|
+
if (targetEl) break;
|
|
302
|
+
}
|
|
303
|
+
if (!targetEl) throw new Error(`Element "${elementId}" not found.`);
|
|
304
|
+
|
|
305
|
+
if (updates.styles) targetEl.styles = { ...targetEl.styles, ...updates.styles };
|
|
306
|
+
if (updates.content) targetEl.content = { ...targetEl.content, ...updates.content };
|
|
307
|
+
saveMemoryDb(db);
|
|
308
|
+
|
|
309
|
+
return { success: true, projectId, elementId, updatedElement: targetEl };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case 'cyrus_list_projects': {
|
|
313
|
+
const list = (db.projects || []).map(p => ({
|
|
314
|
+
id: p.document?.id,
|
|
315
|
+
name: p.document?.name || 'Untitled',
|
|
316
|
+
previewUrl: `https://cyrustudio.vercel.app/#/embed/${p.document?.id}`
|
|
317
|
+
}));
|
|
318
|
+
return { success: true, count: list.length, projects: list };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'cyrus_export_code': {
|
|
322
|
+
const { projectId, framework = 'react-tailwind' } = args;
|
|
323
|
+
const proj = db.projects.find(p => p.document?.id === projectId);
|
|
324
|
+
if (!proj) throw new Error(`Project "${projectId}" not found.`);
|
|
325
|
+
const page = proj.document.pages?.[0] || { name: 'Home', elements: [] };
|
|
326
|
+
return { success: true, projectId, framework, code: generateReactTailwindCode(page) };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
default:
|
|
330
|
+
throw new Error(`Unknown Cyrus MCP tool: "${name}"`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ─── JSON-RPC 2.0 PROTOCOL HANDLER ───────────────────────────────────
|
|
335
|
+
export async function handleMcpJsonRpc(request) {
|
|
336
|
+
if (!request || typeof request !== 'object') {
|
|
337
|
+
return { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const { id, method, params } = request;
|
|
341
|
+
|
|
342
|
+
if (method === 'initialize') {
|
|
343
|
+
return {
|
|
344
|
+
jsonrpc: '2.0',
|
|
345
|
+
id,
|
|
346
|
+
result: {
|
|
347
|
+
protocolVersion: '2024-11-05',
|
|
348
|
+
capabilities: { tools: { listChanged: false } },
|
|
349
|
+
serverInfo: { name: 'cyrus-studio', version: '1.0.8' }
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (method === 'notifications/initialized' || method === 'initialized') return null;
|
|
355
|
+
if (method === 'ping') return { jsonrpc: '2.0', id, result: {} };
|
|
356
|
+
if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: MCP_TOOLS } };
|
|
357
|
+
|
|
358
|
+
if (method === 'tools/call') {
|
|
359
|
+
try {
|
|
360
|
+
const data = await handleToolCall(params?.name, params?.arguments);
|
|
361
|
+
return {
|
|
362
|
+
jsonrpc: '2.0',
|
|
363
|
+
id,
|
|
364
|
+
result: { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], isError: false }
|
|
365
|
+
};
|
|
366
|
+
} catch (err) {
|
|
367
|
+
return {
|
|
368
|
+
jsonrpc: '2.0',
|
|
369
|
+
id,
|
|
370
|
+
result: { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true }
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method "${method}" not found.` } };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ─── STDIO STREAM ───────────────────────────────────────────────────
|
|
379
|
+
if (process.argv.includes('--stdio') || !process.stdin.isTTY || process.argv.includes('mcp-server')) {
|
|
380
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
381
|
+
rl.on('line', async (line) => {
|
|
382
|
+
const trimmed = line.trim();
|
|
383
|
+
if (!trimmed) return;
|
|
384
|
+
try {
|
|
385
|
+
const res = await handleMcpJsonRpc(JSON.parse(trimmed));
|
|
386
|
+
if (res) process.stdout.write(JSON.stringify(res) + '\n');
|
|
387
|
+
} catch (err) {
|
|
388
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: null, error: { message: err.message } }) + '\n');
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
} else {
|
|
392
|
+
console.log('🤖 Cyrus Studio MCP Server v1.0.8');
|
|
393
|
+
console.log('Use with Claude Desktop or Cursor: npx cyrus-studio mcp-server');
|
|
394
|
+
}
|