cyrus-studio 1.0.7 → 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/dist/index.cjs CHANGED
@@ -29,93 +29,1139 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // packages/cyrus-sdk/src/index.ts
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
+ CYRUS_MCP_TOOL_DEFINITIONS: () => CYRUS_MCP_TOOL_DEFINITIONS,
33
+ CyrusCanvasController: () => CyrusCanvasController,
32
34
  CyrusCanvasIframe: () => CyrusCanvasIframe,
35
+ DESIGN_SYSTEM_PRESETS: () => DESIGN_SYSTEM_PRESETS,
36
+ compileCyrusIR: () => compileCyrusIR,
33
37
  createCyrusEmbedUrl: () => createCyrusEmbedUrl,
38
+ executeCyrusMCPTool: () => executeCyrusMCPTool,
39
+ exportToCssVariables: () => exportToCssVariables,
40
+ generateFlutter: () => generateFlutter,
41
+ generateHtmlCss: () => generateHtmlCss,
42
+ generateReactTailwind: () => generateReactTailwind,
43
+ generateSwiftUI: () => generateSwiftUI,
34
44
  getCyrusLLMSystemPrompt: () => getCyrusLLMSystemPrompt,
35
- validateAndRepairCyrusIR: () => validateAndRepairCyrusIR
45
+ importTailwindConfig: () => importTailwindConfig,
46
+ parseFigmaTokens: () => parseFigmaTokens,
47
+ renderHeadlessSVG: () => renderHeadlessSVG,
48
+ validateAndRepairCyrusIR: () => validateAndRepairCyrusIR,
49
+ validateIRHeadless: () => validateIRHeadless
36
50
  });
37
51
  module.exports = __toCommonJS(index_exports);
38
52
  var import_react = __toESM(require("react"), 1);
39
53
 
40
54
  // packages/cyrus-sdk/src/protocol.ts
41
- function validateAndRepairCyrusIR(rawInput) {
55
+ function validateAndRepairCyrusIR(rawInput, fallbackTitle = "AI Application") {
42
56
  let doc = rawInput;
43
57
  if (typeof rawInput === "string") {
58
+ const clean = rawInput.trim();
59
+ const stripped = clean.replace(/```(?:json)?\s*([\s\S]*?)\s*```/gi, "$1").trim();
44
60
  try {
45
- const cleaned = rawInput.replace(/```json\s*/gi, "").replace(/```\s*$/gi, "").trim();
46
- doc = JSON.parse(cleaned);
61
+ doc = JSON.parse(stripped);
47
62
  } catch {
48
- return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
63
+ const start = clean.indexOf("{");
64
+ const end = clean.lastIndexOf("}");
65
+ if (start !== -1 && end !== -1 && end > start) {
66
+ let jsonSub = clean.substring(start, end + 1).replace(/,\s*([\]}])/g, "$1");
67
+ try {
68
+ doc = JSON.parse(jsonSub);
69
+ } catch {
70
+ const openBrackets = (jsonSub.match(/\[/g) || []).length;
71
+ const closeBrackets = (jsonSub.match(/\]/g) || []).length;
72
+ const openBraces = (jsonSub.match(/\{/g) || []).length;
73
+ const closeBraces = (jsonSub.match(/\}/g) || []).length;
74
+ for (let i = 0; i < openBrackets - closeBrackets; i++) jsonSub += "]";
75
+ for (let i = 0; i < openBraces - closeBraces; i++) jsonSub += "}";
76
+ try {
77
+ doc = JSON.parse(jsonSub);
78
+ } catch {
79
+ doc = null;
80
+ }
81
+ }
82
+ }
49
83
  }
50
84
  }
51
85
  if (!doc || typeof doc !== "object") {
52
- return { version: "1.0", projectTitle: "AI Application", pages: [{ name: "Page 1", elements: [] }] };
86
+ return {
87
+ version: "1.0",
88
+ projectTitle: fallbackTitle,
89
+ type: "webapp",
90
+ theme: "dark",
91
+ palette: { primary: "#0071e3", secondary: "#7d2ae8", background: "#09090b", surface: "#18181b", text: "#ffffff" },
92
+ pages: [{ name: "Home", slug: "home", elements: [] }]
93
+ };
53
94
  }
54
- const pages = Array.isArray(doc.pages) ? doc.pages : [{ name: "Home", elements: doc.elements || [] }];
95
+ const rawElements = Array.isArray(doc.elements) ? doc.elements : Array.isArray(doc.pages?.[0]?.elements) ? doc.pages[0].elements : [];
96
+ const sanitizedElements = rawElements.map((el, idx) => {
97
+ return {
98
+ id: el.id || `el_${Date.now()}_${idx}`,
99
+ type: el.type || "card",
100
+ name: el.name || el.content?.title || el.content?.headline || el.type || `Element ${idx + 1}`,
101
+ x: typeof el.x === "number" ? el.x : 0,
102
+ y: typeof el.y === "number" ? el.y : idx * 120,
103
+ width: typeof el.width === "number" ? el.width : 1200,
104
+ height: typeof el.height === "number" ? el.height : 160,
105
+ styles: el.styles || {
106
+ backgroundColor: "#18181b",
107
+ textColor: "#ffffff",
108
+ borderRadius: 12,
109
+ padding: 20
110
+ },
111
+ content: el.content || {}
112
+ };
113
+ });
114
+ const pages = Array.isArray(doc.pages) && doc.pages.length > 0 ? doc.pages.map((p, idx) => ({
115
+ id: p.id || `page_${idx + 1}`,
116
+ name: p.name || `Page ${idx + 1}`,
117
+ slug: p.slug || `page-${idx + 1}`,
118
+ backgroundColor: p.backgroundColor || doc.palette?.background || "#09090b",
119
+ elements: Array.isArray(p.elements) ? p.elements : sanitizedElements
120
+ })) : [{
121
+ id: "page_home",
122
+ name: "Home",
123
+ slug: "home",
124
+ backgroundColor: doc.palette?.background || "#09090b",
125
+ elements: sanitizedElements
126
+ }];
55
127
  return {
56
128
  version: "1.0",
57
- projectTitle: String(doc.projectTitle || doc.name || "AI Application"),
129
+ projectTitle: String(doc.projectTitle || doc.title || doc.name || fallbackTitle),
58
130
  type: doc.type || "webapp",
59
- theme: doc.theme || "light",
60
- palette: doc.palette || { primary: "#0071e3", secondary: "#7d2ae8", background: "#ffffff", surface: "#f5f5f7", text: "#1d1d1f" },
61
- pages: pages.map((p, idx) => ({
62
- name: p.name || `Page ${idx + 1}`,
63
- slug: p.slug || `page-${idx + 1}`,
64
- elements: Array.isArray(p.elements) ? p.elements : []
65
- }))
131
+ theme: doc.theme || "dark",
132
+ palette: doc.palette || { primary: "#0071e3", secondary: "#7d2ae8", background: "#09090b", surface: "#18181b", text: "#ffffff" },
133
+ pages
66
134
  };
67
135
  }
68
- function getCyrusLLMSystemPrompt() {
69
- return `Output your designs strictly in the Cyrus IR JSON format:
136
+ function getCyrusLLMSystemPrompt(options = {}) {
137
+ const theme = options.theme || "dark";
138
+ const type = options.type || "webapp";
139
+ return `You are Cyrus Studio AI, the world's most capable visual UI/UX designer.
140
+ When generating user interfaces, output ONLY valid JSON adhering strictly to the Cyrus IR schema.
141
+
142
+ CANVAS RESOLUTION & LAYOUT:
143
+ - Desktop standard width: 1440px.
144
+ - Use explicit coordinates (x, y, width, height) stacked vertically without overlapping.
145
+ - Use composite component types for realistic, high-fidelity interfaces.
146
+
147
+ SUPPORTED COMPONENT TYPES:
148
+ 1. "navbar": Navigation header with brandName, icon, links array, buttonText.
149
+ 2. "hero" / "heroSection": Hero section with badge, headline, subhead, buttonText, secondaryButtonText, imageUrl.
150
+ 3. "featureCard" / "featureSection": Feature card with title, subtitle, description/text, icon, buttonText.
151
+ 4. "pricingCard": Pricing tier with planName, price, badge, features array, buttonText.
152
+ 5. "metricCard" / "statCard" / "kpiCard": KPI card with value, title/label, trend (e.g. "+14.2%"), icon.
153
+ 6. "testimonialCard": Customer quote with text, author (title), role (subtitle), avatar (imageUrl), rating (1-5).
154
+ 7. "productCard": E-commerce card with title, price, text (description), imageUrl, badge, buttonText.
155
+ 8. "chart" / "lineChart" / "barChart" / "areaChart": Data visualization cards with title and subtitle.
156
+ 9. "table" / "dataTable": Data grid component.
157
+ 10. "footer": Footer section with brandName, copyright text, and links array.
158
+ 11. "image": Dedicated image component with imageUrl, alt, borderRadius.
159
+ 12. "button" / "badge": Interactive buttons and status chips with label/text and icon.
160
+
161
+ AVAILABLE ICONS:
162
+ "Sparkles", "Cpu", "Zap", "Flame", "Shield", "Activity", "Layers", "ArrowRight", "Database", "Terminal", "Star", "Users", "Globe", "Lock", "Cloud", "Code2", "Heart", "TrendingUp", "TrendingDown", "Rocket", "Settings", "Package", "Camera", "Music", "Phone", "Mail", "Search", "Home", "Menu", "Bell", "CreditCard", "ShoppingBag", "Award", "CheckCircle2", "Download", "Filter", "Grid", "List"
163
+
164
+ OUTPUT FORMAT (Valid JSON only):
70
165
  {
71
166
  "version": "1.0",
72
- "projectTitle": "Title",
73
- "type": "webapp",
74
- "theme": "light",
167
+ "projectTitle": "Application Title",
168
+ "type": "${type}",
169
+ "theme": "${theme}",
75
170
  "pages": [
76
- { "name": "Home", "elements": [{ "type": "navbar" }, { "type": "heading" }, { "type": "button" }] }
171
+ {
172
+ "name": "Home",
173
+ "elements": [
174
+ {
175
+ "type": "navbar",
176
+ "name": "Navbar",
177
+ "x": 0,
178
+ "y": 0,
179
+ "width": 1440,
180
+ "height": 72,
181
+ "styles": {
182
+ "backgroundColor": "#0f0f11",
183
+ "textColor": "#ffffff",
184
+ "borderRadius": 0
185
+ },
186
+ "content": {
187
+ "brandName": "Acme Inc",
188
+ "icon": "Sparkles",
189
+ "links": ["Features", "Pricing", "Docs"],
190
+ "buttonText": "Get Started"
191
+ }
192
+ }
193
+ ]
194
+ }
77
195
  ]
78
196
  }`;
79
197
  }
80
198
 
199
+ // packages/cyrus-sdk/src/mcp.ts
200
+ var CYRUS_MCP_TOOL_DEFINITIONS = [
201
+ {
202
+ name: "cyrus_create_element",
203
+ description: "Create and place a new UI widget/element on the Cyrus visual canvas (e.g. navbar, hero, button, pricingCard, featureCard, metricCard, chart).",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ type: {
208
+ type: "string",
209
+ description: "Element type: navbar, hero, button, badge, input, searchBar, pricingCard, featureCard, testimonialCard, metricCard, lineChart, footer, container, etc."
210
+ },
211
+ name: { type: "string", description: "Human-readable element name" },
212
+ x: { type: "number", description: "X position in pixels" },
213
+ y: { type: "number", description: "Y position in pixels" },
214
+ width: { type: "number", description: "Width in pixels (e.g. 1440 for full-width navbar/hero)" },
215
+ height: { type: "number", description: "Height in pixels" },
216
+ styles: {
217
+ type: "object",
218
+ description: "Visual styles (backgroundColor, textColor, borderRadius, padding, shadow, borderColor, fontSize)"
219
+ },
220
+ content: {
221
+ type: "object",
222
+ description: "Component content properties (headline, title, subtitle, buttonText, icon, brandName, price, features, imageUrl)"
223
+ }
224
+ },
225
+ required: ["type"]
226
+ }
227
+ },
228
+ {
229
+ name: "cyrus_update_styles",
230
+ description: "Update visual styles (colors, typography, border radius, padding, background gradients) of an existing canvas element.",
231
+ inputSchema: {
232
+ type: "object",
233
+ properties: {
234
+ elementId: { type: "string", description: "Unique ID of the canvas element" },
235
+ styles: {
236
+ type: "object",
237
+ description: 'Updated style dictionary (e.g. { backgroundColor: "#0071e3", borderRadius: 16 })'
238
+ }
239
+ },
240
+ required: ["elementId", "styles"]
241
+ }
242
+ },
243
+ {
244
+ name: "cyrus_update_content",
245
+ description: "Update text, headlines, badges, button copy, icons, prices, or images of an existing canvas element.",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ elementId: { type: "string", description: "Unique ID of the canvas element" },
250
+ content: {
251
+ type: "object",
252
+ description: 'Updated content dictionary (e.g. { headline: "Next-Gen AI", buttonText: "Get Started" })'
253
+ }
254
+ },
255
+ required: ["elementId", "content"]
256
+ }
257
+ },
258
+ {
259
+ name: "cyrus_apply_design_system",
260
+ description: "Apply a global design system token set (colors, primary brand, fonts, border radii, presets) across the entire canvas.",
261
+ inputSchema: {
262
+ type: "object",
263
+ properties: {
264
+ preset: {
265
+ type: "string",
266
+ enum: ["shadcn-zinc", "shadcn-slate", "shadcn-violet", "shadcn-rose", "apple-hig", "tailwind-cyberpunk"],
267
+ description: "Pre-configured design system preset"
268
+ },
269
+ colors: {
270
+ type: "object",
271
+ description: "Custom color overrides (primary, secondary, background, foreground, border)"
272
+ },
273
+ radii: {
274
+ type: "object",
275
+ description: "Corner radiuses (sm, md, lg, xl, full)"
276
+ }
277
+ }
278
+ }
279
+ },
280
+ {
281
+ name: "cyrus_batch_update_elements",
282
+ description: "Apply updates to multiple elements atomically in one step.",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: {
286
+ updates: {
287
+ type: "array",
288
+ items: {
289
+ type: "object",
290
+ properties: {
291
+ id: { type: "string" },
292
+ styles: { type: "object" },
293
+ content: { type: "object" },
294
+ x: { type: "number" },
295
+ y: { type: "number" },
296
+ width: { type: "number" },
297
+ height: { type: "number" }
298
+ },
299
+ required: ["id"]
300
+ }
301
+ }
302
+ },
303
+ required: ["updates"]
304
+ }
305
+ },
306
+ {
307
+ name: "cyrus_get_canvas_document",
308
+ description: "Get the full structured Cyrus IR JSON document of the current canvas for analysis or code generation.",
309
+ inputSchema: {
310
+ type: "object",
311
+ properties: {}
312
+ }
313
+ },
314
+ {
315
+ name: "cyrus_take_canvas_snapshot",
316
+ description: "Request a visual layout snapshot / SVG representation for multimodal inspection and layout validation.",
317
+ inputSchema: {
318
+ type: "object",
319
+ properties: {
320
+ format: { type: "string", enum: ["svg", "json", "png"], default: "svg" }
321
+ }
322
+ }
323
+ },
324
+ {
325
+ name: "cyrus_stream_elements",
326
+ description: "Stream elements onto the canvas progressively as the AI generates them (real-time materialization).",
327
+ inputSchema: {
328
+ type: "object",
329
+ properties: {
330
+ elements: {
331
+ type: "array",
332
+ description: "Batch of incremental elements to render with spring animation"
333
+ },
334
+ isComplete: { type: "boolean", description: "Whether streaming generation has finished" }
335
+ },
336
+ required: ["elements"]
337
+ }
338
+ }
339
+ ];
340
+ async function executeCyrusMCPTool(controller, toolCall) {
341
+ const { name, arguments: args, id } = toolCall;
342
+ switch (name) {
343
+ case "cyrus_create_element": {
344
+ controller.sendMessage({
345
+ type: "CYRUS_CREATE_ELEMENT",
346
+ payload: {
347
+ element: {
348
+ type: args.type,
349
+ name: args.name,
350
+ x: args.x,
351
+ y: args.y,
352
+ width: args.width,
353
+ height: args.height,
354
+ styles: args.styles,
355
+ content: args.content
356
+ },
357
+ select: true
358
+ }
359
+ });
360
+ return { toolCallId: id, success: true, data: { created: true, type: args.type } };
361
+ }
362
+ case "cyrus_update_styles": {
363
+ controller.sendMessage({
364
+ type: "CYRUS_UPDATE_ELEMENT",
365
+ payload: {
366
+ id: args.elementId,
367
+ updates: { styles: args.styles }
368
+ }
369
+ });
370
+ return { toolCallId: id, success: true, data: { updated: true, elementId: args.elementId } };
371
+ }
372
+ case "cyrus_update_content": {
373
+ controller.sendMessage({
374
+ type: "CYRUS_UPDATE_ELEMENT",
375
+ payload: {
376
+ id: args.elementId,
377
+ updates: { content: args.content }
378
+ }
379
+ });
380
+ return { toolCallId: id, success: true, data: { updated: true, elementId: args.elementId } };
381
+ }
382
+ case "cyrus_apply_design_system": {
383
+ controller.sendMessage({
384
+ type: "CYRUS_SET_DESIGN_TOKENS",
385
+ payload: args
386
+ });
387
+ return { toolCallId: id, success: true, data: { appliedPreset: args.preset || "custom" } };
388
+ }
389
+ case "cyrus_batch_update_elements": {
390
+ if (Array.isArray(args.updates)) {
391
+ for (const update of args.updates) {
392
+ controller.sendMessage({
393
+ type: "CYRUS_UPDATE_ELEMENT",
394
+ payload: {
395
+ id: update.id,
396
+ updates: {
397
+ styles: update.styles,
398
+ content: update.content,
399
+ x: update.x,
400
+ y: update.y,
401
+ width: update.width,
402
+ height: update.height
403
+ }
404
+ }
405
+ });
406
+ }
407
+ }
408
+ return { toolCallId: id, success: true, data: { updatedCount: args.updates?.length || 0 } };
409
+ }
410
+ case "cyrus_stream_elements": {
411
+ controller.sendMessage({
412
+ type: "CYRUS_STREAM_INCREMENTAL_ELEMENTS",
413
+ payload: {
414
+ elements: args.elements || [],
415
+ isComplete: Boolean(args.isComplete)
416
+ }
417
+ });
418
+ return { toolCallId: id, success: true, data: { streamedCount: args.elements?.length || 0 } };
419
+ }
420
+ case "cyrus_take_canvas_snapshot": {
421
+ controller.sendMessage({
422
+ type: "CYRUS_REQUEST_CANVAS_SNAPSHOT",
423
+ payload: { format: args.format || "svg" }
424
+ });
425
+ return { toolCallId: id, success: true, data: { requested: true } };
426
+ }
427
+ default:
428
+ return {
429
+ toolCallId: id,
430
+ success: false,
431
+ error: `Unknown Cyrus MCP Tool: ${name}`
432
+ };
433
+ }
434
+ }
435
+
436
+ // packages/cyrus-sdk/src/tokens.ts
437
+ var DESIGN_SYSTEM_PRESETS = {
438
+ "shadcn-zinc": {
439
+ preset: "shadcn-zinc",
440
+ colors: {
441
+ primary: "#18181b",
442
+ primaryForeground: "#fafafa",
443
+ secondary: "#27272a",
444
+ secondaryForeground: "#fafafa",
445
+ accent: "#0071e3",
446
+ accentForeground: "#ffffff",
447
+ background: "#09090b",
448
+ foreground: "#f4f4f5",
449
+ surface: "#121215",
450
+ card: "#18181b",
451
+ cardForeground: "#f4f4f5",
452
+ muted: "#27272a",
453
+ mutedForeground: "#a1a1aa",
454
+ border: "rgba(255, 255, 255, 0.1)",
455
+ ring: "#0071e3"
456
+ },
457
+ radii: { sm: 6, md: 8, lg: 12, xl: 16, full: 9999 },
458
+ typography: {
459
+ fontFamilySans: "Inter, system-ui, -apple-system, sans-serif",
460
+ baseFontSize: 14
461
+ }
462
+ },
463
+ "shadcn-slate": {
464
+ preset: "shadcn-slate",
465
+ colors: {
466
+ primary: "#0f172a",
467
+ primaryForeground: "#f8fafc",
468
+ secondary: "#1e293b",
469
+ secondaryForeground: "#f8fafc",
470
+ accent: "#38bdf8",
471
+ accentForeground: "#0f172a",
472
+ background: "#020617",
473
+ foreground: "#f8fafc",
474
+ surface: "#0f172a",
475
+ card: "#1e293b",
476
+ cardForeground: "#f8fafc",
477
+ muted: "#334155",
478
+ mutedForeground: "#94a3b8",
479
+ border: "rgba(148, 163, 184, 0.15)",
480
+ ring: "#38bdf8"
481
+ },
482
+ radii: { sm: 4, md: 6, lg: 10, xl: 14, full: 9999 }
483
+ },
484
+ "shadcn-violet": {
485
+ preset: "shadcn-violet",
486
+ colors: {
487
+ primary: "#7c3aed",
488
+ primaryForeground: "#ffffff",
489
+ secondary: "#4c1d95",
490
+ secondaryForeground: "#f5f3ff",
491
+ accent: "#a78bfa",
492
+ accentForeground: "#2e1065",
493
+ background: "#0f0a1c",
494
+ foreground: "#f5f3ff",
495
+ surface: "#1a1033",
496
+ card: "#241447",
497
+ cardForeground: "#f5f3ff",
498
+ muted: "#2e1065",
499
+ mutedForeground: "#c4b5fd",
500
+ border: "rgba(167, 139, 250, 0.2)",
501
+ ring: "#7c3aed"
502
+ },
503
+ radii: { sm: 8, md: 12, lg: 16, xl: 24, full: 9999 }
504
+ },
505
+ "shadcn-rose": {
506
+ preset: "shadcn-rose",
507
+ colors: {
508
+ primary: "#e11d48",
509
+ primaryForeground: "#ffffff",
510
+ secondary: "#4c0519",
511
+ secondaryForeground: "#ffe4e6",
512
+ accent: "#fb7185",
513
+ accentForeground: "#881337",
514
+ background: "#0f0508",
515
+ foreground: "#fff1f2",
516
+ surface: "#1c0b11",
517
+ card: "#2a111a",
518
+ cardForeground: "#fff1f2",
519
+ muted: "#4c0519",
520
+ mutedForeground: "#fda4af",
521
+ border: "rgba(251, 113, 133, 0.2)",
522
+ ring: "#e11d48"
523
+ },
524
+ radii: { sm: 6, md: 10, lg: 14, xl: 18, full: 9999 }
525
+ },
526
+ "apple-hig": {
527
+ preset: "apple-hig",
528
+ colors: {
529
+ primary: "#0071e3",
530
+ primaryForeground: "#ffffff",
531
+ secondary: "#5e5ce6",
532
+ secondaryForeground: "#ffffff",
533
+ accent: "#30b0c7",
534
+ accentForeground: "#ffffff",
535
+ background: "#000000",
536
+ foreground: "#f5f5f7",
537
+ surface: "#1c1c1e",
538
+ card: "#2c2c2e",
539
+ cardForeground: "#f5f5f7",
540
+ muted: "#3a3a3c",
541
+ mutedForeground: "#86868b",
542
+ border: "rgba(255, 255, 255, 0.12)",
543
+ ring: "#0071e3"
544
+ },
545
+ radii: { sm: 8, md: 12, lg: 16, xl: 22, full: 9999 },
546
+ typography: {
547
+ fontFamilySans: '-apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif',
548
+ baseFontSize: 15
549
+ }
550
+ },
551
+ "tailwind-cyberpunk": {
552
+ preset: "tailwind-cyberpunk",
553
+ colors: {
554
+ primary: "#00ffcc",
555
+ primaryForeground: "#050508",
556
+ secondary: "#ff007f",
557
+ secondaryForeground: "#ffffff",
558
+ accent: "#ffe600",
559
+ accentForeground: "#000000",
560
+ background: "#08080f",
561
+ foreground: "#00ffcc",
562
+ surface: "#101020",
563
+ card: "#16162d",
564
+ cardForeground: "#ffffff",
565
+ muted: "#202040",
566
+ mutedForeground: "#00c8ff",
567
+ border: "#ff007f",
568
+ ring: "#00ffcc"
569
+ },
570
+ radii: { sm: 2, md: 4, lg: 6, xl: 8, full: 0 }
571
+ }
572
+ };
573
+ function parseFigmaTokens(rawTokensJson) {
574
+ const data = typeof rawTokensJson === "string" ? JSON.parse(rawTokensJson) : rawTokensJson;
575
+ const tokens = {
576
+ preset: "custom",
577
+ colors: {},
578
+ radii: {},
579
+ typography: {}
580
+ };
581
+ const extractVal = (val) => {
582
+ if (!val) return "";
583
+ if (typeof val === "string") return val;
584
+ if (typeof val === "object" && val.value) return extractVal(val.value);
585
+ return String(val);
586
+ };
587
+ const traverse = (obj, path = []) => {
588
+ if (!obj || typeof obj !== "object") return;
589
+ for (const [key, val] of Object.entries(obj)) {
590
+ const currentPath = [...path, key.toLowerCase()];
591
+ const pathStr = currentPath.join(".");
592
+ if (val && typeof val === "object" && "value" in val) {
593
+ const rawValue = extractVal(val.value);
594
+ if (pathStr.includes("color") || pathStr.includes("primary") || pathStr.includes("background")) {
595
+ if (pathStr.includes("primary")) tokens.colors.primary = rawValue;
596
+ else if (pathStr.includes("secondary")) tokens.colors.secondary = rawValue;
597
+ else if (pathStr.includes("background") || pathStr.includes("bg")) tokens.colors.background = rawValue;
598
+ else if (pathStr.includes("surface") || pathStr.includes("card")) tokens.colors.card = rawValue;
599
+ else if (pathStr.includes("text") || pathStr.includes("foreground")) tokens.colors.foreground = rawValue;
600
+ else if (pathStr.includes("accent")) tokens.colors.accent = rawValue;
601
+ else if (pathStr.includes("border")) tokens.colors.border = rawValue;
602
+ else tokens.colors[key] = rawValue;
603
+ } else if (pathStr.includes("radius") || pathStr.includes("rounded")) {
604
+ const num = parseInt(rawValue, 10);
605
+ if (!isNaN(num)) {
606
+ if (pathStr.includes("sm")) tokens.radii.sm = num;
607
+ else if (pathStr.includes("md")) tokens.radii.md = num;
608
+ else if (pathStr.includes("lg")) tokens.radii.lg = num;
609
+ else if (pathStr.includes("xl")) tokens.radii.xl = num;
610
+ }
611
+ }
612
+ } else {
613
+ traverse(val, currentPath);
614
+ }
615
+ }
616
+ };
617
+ traverse(data);
618
+ return tokens;
619
+ }
620
+ function importTailwindConfig(tailwindConfig) {
621
+ const config = typeof tailwindConfig === "string" ? JSON.parse(tailwindConfig) : tailwindConfig;
622
+ const themeColors = config?.theme?.extend?.colors || config?.theme?.colors || {};
623
+ const themeRadii = config?.theme?.extend?.borderRadius || config?.theme?.borderRadius || {};
624
+ const colors = {};
625
+ for (const [k, v] of Object.entries(themeColors)) {
626
+ if (typeof v === "string") colors[k] = v;
627
+ else if (typeof v === "object" && v !== null) {
628
+ if ("DEFAULT" in v) colors[k] = v.DEFAULT;
629
+ else if ("500" in v) colors[k] = v["500"];
630
+ else if ("600" in v) colors[k] = v["600"];
631
+ }
632
+ }
633
+ return {
634
+ preset: "custom",
635
+ colors: {
636
+ primary: colors.primary || colors.blue || "#0071e3",
637
+ secondary: colors.secondary || colors.purple || "#7d2ae8",
638
+ background: colors.background || "#09090b",
639
+ foreground: colors.foreground || "#ffffff",
640
+ surface: colors.surface || colors.card || "#18181b",
641
+ border: colors.border || "rgba(255,255,255,0.1)",
642
+ ...colors
643
+ },
644
+ radii: {
645
+ sm: parseInt(String(themeRadii.sm || 6), 10) || 6,
646
+ md: parseInt(String(themeRadii.md || 10), 10) || 10,
647
+ lg: parseInt(String(themeRadii.lg || 14), 10) || 14,
648
+ xl: parseInt(String(themeRadii.xl || 20), 10) || 20
649
+ }
650
+ };
651
+ }
652
+ function exportToCssVariables(tokens) {
653
+ const vars = [":root {"];
654
+ if (tokens.colors) {
655
+ for (const [k, v] of Object.entries(tokens.colors)) {
656
+ if (v) vars.push(` --color-${k}: ${v};`);
657
+ }
658
+ }
659
+ if (tokens.radii) {
660
+ for (const [k, v] of Object.entries(tokens.radii)) {
661
+ if (typeof v === "number") vars.push(` --radius-${k}: ${v}px;`);
662
+ }
663
+ }
664
+ vars.push("}");
665
+ return vars.join("\n");
666
+ }
667
+
668
+ // packages/cyrus-sdk/src/generators.ts
669
+ function escapeString(str) {
670
+ return (str || "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
671
+ }
672
+ function generateReactTailwind(doc) {
673
+ const page = doc.pages[0] || { name: "Home", elements: [] };
674
+ const elements = page.elements || [];
675
+ return `import React from 'react';
676
+ import { Sparkles, ArrowRight, CheckCircle2, TrendingUp, Star, Globe, Shield, Zap, Cpu } from 'lucide-react';
677
+
678
+ export default function ${doc.projectTitle.replace(/[^a-zA-Z0-9]/g, "") || "App"}() {
679
+ return (
680
+ <div className="min-h-screen bg-[${doc.palette?.background || "#09090b"}] text-[${doc.palette?.text || "#ffffff"}] font-sans antialiased overflow-x-hidden selection:bg-[#0071e3]/30">
681
+ <div className="max-w-[1440px] mx-auto px-6 py-8 space-y-12">
682
+ ${elements.map((el) => renderReactElement(el)).join("\n\n")}
683
+ </div>
684
+ </div>
685
+ );
686
+ }
687
+ `;
688
+ }
689
+ function renderReactElement(el) {
690
+ const c = el.content || {};
691
+ const s = el.styles || {};
692
+ const bg = s.backgroundGradient || s.backgroundColor || "#18181b";
693
+ const tc = s.textColor || "#ffffff";
694
+ const radius = s.borderRadius ?? 14;
695
+ switch (el.type) {
696
+ case "navbar":
697
+ return ` {/* Navbar */}
698
+ <header className="w-full flex items-center justify-between px-8 py-4 rounded-[${radius}px] bg-[${bg}] text-[${tc}] border border-white/10 backdrop-blur-md shadow-lg">
699
+ <div className="flex items-center gap-3">
700
+ <div className="w-9 h-9 rounded-xl bg-[#0071e3] text-white flex items-center justify-center font-bold">
701
+ <Sparkles className="w-5 h-5" />
702
+ </div>
703
+ <span className="text-lg font-bold tracking-tight">${escapeString(c.brandName || c.title || "Brand")}</span>
704
+ </div>
705
+ <nav className="hidden md:flex items-center gap-8 text-sm font-medium text-white/80">
706
+ ${(c.links || ["Features", "Solutions", "Pricing", "Docs"]).map((l) => `<a href="#${l.toLowerCase()}" className="hover:text-white transition-colors">${escapeString(l)}</a>`).join("\n ")}
707
+ </nav>
708
+ ${c.buttonText ? `<button className="px-5 py-2 rounded-xl bg-[#0071e3] hover:bg-[#0077ed] text-white text-sm font-semibold shadow-md transition-all active:scale-95">${escapeString(c.buttonText)}</button>` : ""}
709
+ </header>`;
710
+ case "hero":
711
+ case "heroSection":
712
+ return ` {/* Hero Section */}
713
+ <section className="relative overflow-hidden rounded-[${radius}px] px-8 py-20 md:py-28 text-center flex flex-col items-center justify-center bg-[${bg}] border border-white/10 shadow-2xl">
714
+ ${c.badge ? `<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white/10 text-white/90 text-xs font-semibold uppercase tracking-wider mb-6 border border-white/15 backdrop-blur-md">${escapeString(c.badge)}</div>` : ""}
715
+ <h1 className="text-4xl md:text-6xl font-extrabold tracking-tight max-w-4xl text-white leading-tight">
716
+ ${escapeString(c.headline || c.title || "Build the Future of Web Applications")}
717
+ </h1>
718
+ <p className="mt-6 text-lg md:text-xl text-white/70 max-w-2xl leading-relaxed">
719
+ ${escapeString(c.subhead || c.subtitle || c.text || "Experience seamless visual design powered by generative artificial intelligence.")}
720
+ </p>
721
+ <div className="mt-10 flex flex-wrap items-center justify-center gap-4">
722
+ <button className="px-8 py-3.5 rounded-xl bg-[#0071e3] hover:bg-[#0077ed] text-white font-semibold shadow-lg transition-all active:scale-95 flex items-center gap-2">
723
+ <span>${escapeString(c.buttonText || "Get Started Free")}</span>
724
+ <ArrowRight className="w-4 h-4" />
725
+ </button>
726
+ ${c.secondaryButtonText ? `<button className="px-8 py-3.5 rounded-xl bg-white/10 hover:bg-white/15 text-white font-semibold border border-white/20 backdrop-blur-md transition-all active:scale-95">${escapeString(c.secondaryButtonText)}</button>` : ""}
727
+ </div>
728
+ ${c.imageUrl ? `<div className="mt-14 w-full max-w-5xl rounded-2xl overflow-hidden border border-white/15 shadow-2xl"><img src="${escapeString(c.imageUrl)}" alt="Preview" className="w-full h-auto object-cover" /></div>` : ""}
729
+ </section>`;
730
+ case "pricingCard":
731
+ return ` {/* Pricing Card */}
732
+ <div className="rounded-[${radius}px] p-8 bg-[${bg}] text-[${tc}] border border-white/10 shadow-xl flex flex-col justify-between">
733
+ <div>
734
+ ${c.badge ? `<span className="inline-block px-3 py-1 rounded-full bg-[#0071e3]/20 text-[#0071e3] text-xs font-semibold mb-4">${escapeString(c.badge)}</span>` : ""}
735
+ <h3 className="text-xl font-bold">${escapeString(c.planName || c.title || "Pro Plan")}</h3>
736
+ <div className="mt-4 flex items-baseline gap-1">
737
+ <span className="text-4xl font-extrabold text-[#0071e3]">${escapeString(c.price || "$29/mo")}</span>
738
+ </div>
739
+ <ul className="mt-8 space-y-3 text-sm text-white/80">
740
+ ${(c.features || ["Unlimited Projects", "Custom Domains", "Priority 24/7 Support", "Team Collaboration"]).map((f) => `<li className="flex items-center gap-2.5"><CheckCircle2 className="w-4 h-4 text-[#34c759] shrink-0" /><span>${escapeString(f)}</span></li>`).join("\n ")}
741
+ </ul>
742
+ </div>
743
+ <button className="mt-8 w-full py-3 rounded-xl bg-[#0071e3] hover:bg-[#0077ed] text-white font-semibold shadow-md transition-all active:scale-95">
744
+ ${escapeString(c.buttonText || "Choose Plan")}
745
+ </button>
746
+ </div>`;
747
+ case "metricCard":
748
+ case "statCard":
749
+ return ` {/* Metric Card */}
750
+ <div className="rounded-[${radius}px] p-6 bg-[${bg}] text-[${tc}] border border-white/10 shadow-lg flex items-center justify-between">
751
+ <div>
752
+ <p className="text-sm font-medium text-white/60">${escapeString(c.title || c.label || "Total Metric")}</p>
753
+ <p className="text-3xl font-extrabold text-white mt-1">${escapeString(c.value || "100%")}</p>
754
+ ${c.trend ? `<p className="text-xs font-semibold text-[#34c759] mt-2 flex items-center gap-1"><TrendingUp className="w-3.5 h-3.5" /> ${escapeString(c.trend)}</p>` : ""}
755
+ </div>
756
+ <div className="w-12 h-12 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center text-[#0071e3]">
757
+ <Zap className="w-6 h-6" />
758
+ </div>
759
+ </div>`;
760
+ case "footer":
761
+ return ` {/* Footer */}
762
+ <footer className="w-full py-12 border-t border-white/10 flex flex-col md:flex-row items-center justify-between gap-6 text-sm text-white/60">
763
+ <div className="flex items-center gap-2">
764
+ <span className="font-bold text-white">${escapeString(c.brandName || "Cyrus")}</span>
765
+ <span>\u2014 ${escapeString(c.text || c.copyright || "\xA9 2026 All rights reserved.")}</span>
766
+ </div>
767
+ <div className="flex items-center gap-6">
768
+ ${(c.links || ["Privacy Policy", "Terms of Service", "Support", "GitHub"]).map((l) => `<a href="#" className="hover:text-white transition-colors">${escapeString(l)}</a>`).join("\n ")}
769
+ </div>
770
+ </footer>`;
771
+ default:
772
+ return ` {/* ${el.name || el.type} */}
773
+ <div className="rounded-[${radius}px] p-6 bg-[${bg}] text-[${tc}] border border-white/10 shadow-md">
774
+ <h4 className="text-lg font-bold">${escapeString(c.title || c.headline || el.name || "Component")}</h4>
775
+ <p className="text-sm text-white/70 mt-2">${escapeString(c.text || c.description || c.subtitle || "")}</p>
776
+ </div>`;
777
+ }
778
+ }
779
+ function generateHtmlCss(doc) {
780
+ return `<!DOCTYPE html>
781
+ <html lang="en">
782
+ <head>
783
+ <meta charset="UTF-8" />
784
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
785
+ <title>${escapeString(doc.projectTitle)}</title>
786
+ <link rel="preconnect" href="https://fonts.googleapis.com">
787
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
788
+ <style>
789
+ * { box-sizing: border-box; margin: 0; padding: 0; }
790
+ body {
791
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
792
+ background-color: ${doc.palette?.background || "#09090b"};
793
+ color: ${doc.palette?.text || "#ffffff"};
794
+ min-height: 100vh;
795
+ padding: 32px 24px;
796
+ }
797
+ .container { max-width: 1440px; margin: 0 auto; display: flex; flex-direction: column; gap: 32px; }
798
+ .card { background: #18181b; border: 1px solid rgba(255,255,255,0.1); border-radius: 14px; padding: 24px; }
799
+ .btn { background: #0071e3; color: #ffffff; border: none; border-radius: 10px; padding: 10px 20px; font-weight: 600; cursor: pointer; }
800
+ </style>
801
+ </head>
802
+ <body>
803
+ <div class="container">
804
+ <header class="card" style="display: flex; justify-content: space-between; align-items: center;">
805
+ <h2 style="font-weight: 800;">${escapeString(doc.projectTitle)}</h2>
806
+ <button class="btn">Get Started</button>
807
+ </header>
808
+ </div>
809
+ </body>
810
+ </html>`;
811
+ }
812
+ function generateFlutter(doc) {
813
+ return `import 'package:flutter/material.dart';
814
+
815
+ void main() => runApp(const CyrusGeneratedApp());
816
+
817
+ class CyrusGeneratedApp extends StatelessWidget {
818
+ const CyrusGeneratedApp({super.key});
819
+
820
+ @override
821
+ Widget build(BuildContext context) {
822
+ return MaterialApp(
823
+ title: '${escapeString(doc.projectTitle)}',
824
+ theme: ThemeData.dark().copyWith(
825
+ scaffoldBackgroundColor: const Color(0xFF09090B),
826
+ ),
827
+ home: const HomePage(),
828
+ );
829
+ }
830
+ }
831
+
832
+ class HomePage extends StatelessWidget {
833
+ const HomePage({super.key});
834
+
835
+ @override
836
+ Widget build(BuildContext context) {
837
+ return Scaffold(
838
+ appBar: AppBar(
839
+ title: const Text('${escapeString(doc.projectTitle)}'),
840
+ backgroundColor: const Color(0xFF18181B),
841
+ ),
842
+ body: const Center(
843
+ child: Text(
844
+ 'Exported with Cyrus Studio',
845
+ style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
846
+ ),
847
+ ),
848
+ );
849
+ }
850
+ }
851
+ `;
852
+ }
853
+ function generateSwiftUI(doc) {
854
+ return `import SwiftUI
855
+
856
+ struct ContentView: View {
857
+ var body: some View {
858
+ ZStack {
859
+ Color(red: 0.04, green: 0.04, blue: 0.05).ignoresSafeArea()
860
+ VStack(spacing: 20) {
861
+ Text("${escapeString(doc.projectTitle)}")
862
+ .font(.largeTitle)
863
+ .fontWeight(.bold)
864
+ .foregroundColor(.white)
865
+ }
866
+ .padding()
867
+ }
868
+ }
869
+ }
870
+ `;
871
+ }
872
+
873
+ // packages/cyrus-sdk/src/compiler.ts
874
+ function compileCyrusIR(rawDocument, options = {}) {
875
+ const doc = validateAndRepairCyrusIR(rawDocument);
876
+ const framework = options.framework || "react-tailwind";
877
+ switch (framework) {
878
+ case "react-tailwind":
879
+ return generateReactTailwind(doc);
880
+ case "flutter":
881
+ return generateFlutter(doc);
882
+ case "swiftui":
883
+ return generateSwiftUI(doc);
884
+ case "html-css":
885
+ return generateHtmlCss(doc);
886
+ default:
887
+ return generateReactTailwind(doc);
888
+ }
889
+ }
890
+ function validateIRHeadless(rawJson) {
891
+ const warnings = [];
892
+ const doc = validateAndRepairCyrusIR(rawJson);
893
+ let elementCount = 0;
894
+ doc.pages.forEach((p, pIdx) => {
895
+ if (!p.name) warnings.push(`Page index ${pIdx} is missing a name`);
896
+ elementCount += p.elements?.length || 0;
897
+ p.elements?.forEach((el, eIdx) => {
898
+ if (!el.type) warnings.push(`Page ${p.name || pIdx} Element ${eIdx} has undefined type`);
899
+ if (typeof el.x !== "number" || typeof el.y !== "number") {
900
+ warnings.push(`Page ${p.name || pIdx} Element ${el.name || el.type} missing explicit (x, y) coordinates`);
901
+ }
902
+ });
903
+ });
904
+ return {
905
+ valid: warnings.length === 0,
906
+ document: doc,
907
+ elementCount,
908
+ pageCount: doc.pages.length,
909
+ warnings
910
+ };
911
+ }
912
+ function renderHeadlessSVG(rawDocument, pageIndex = 0, viewportWidth = 1440) {
913
+ const doc = validateAndRepairCyrusIR(rawDocument);
914
+ const page = doc.pages[pageIndex] || doc.pages[0] || { elements: [] };
915
+ const bg = page.backgroundColor || doc.palette?.background || "#09090b";
916
+ let maxY = 900;
917
+ page.elements.forEach((el) => {
918
+ const bottom = (el.y || 0) + (el.height || 100);
919
+ if (bottom > maxY) maxY = bottom + 80;
920
+ });
921
+ const svgElements = [];
922
+ page.elements.forEach((el) => {
923
+ const x = el.x || 0;
924
+ const y = el.y || 0;
925
+ const w = el.width || 200;
926
+ const h = el.height || 100;
927
+ const bgCol = el.styles?.backgroundColor || "#18181b";
928
+ const textCol = el.styles?.textColor || "#ffffff";
929
+ const radius = el.styles?.borderRadius ?? 8;
930
+ const title = el.content?.headline || el.content?.title || el.content?.brandName || el.content?.text || el.name || el.type;
931
+ svgElements.push(`
932
+ <g transform="translate(${x}, ${y})">
933
+ <rect width="${w}" height="${h}" rx="${radius}" fill="${bgCol}" stroke="rgba(255,255,255,0.08)" stroke-width="1" />
934
+ <text x="20" y="${Math.min(h / 2 + 6, 36)}" fill="${textCol}" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="600">
935
+ ${escapeXml(String(title))}
936
+ </text>
937
+ <text x="20" y="${Math.min(h / 2 + 26, 60)}" fill="${textCol}" fill-opacity="0.6" font-family="system-ui, -apple-system, sans-serif" font-size="12">
938
+ Type: ${escapeXml(el.type)} (${w}x${h})
939
+ </text>
940
+ </g>`);
941
+ });
942
+ return `<?xml version="1.0" encoding="UTF-8"?>
943
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${viewportWidth} ${maxY}" width="${viewportWidth}" height="${maxY}">
944
+ <rect width="100%" height="100%" fill="${bg}" />
945
+ ${svgElements.join("\n")}
946
+ </svg>`;
947
+ }
948
+ function escapeXml(unsafe) {
949
+ return unsafe.replace(/[<>&'"]/g, (c) => {
950
+ switch (c) {
951
+ case "<":
952
+ return "&lt;";
953
+ case ">":
954
+ return "&gt;";
955
+ case "&":
956
+ return "&amp;";
957
+ case "'":
958
+ return "&apos;";
959
+ case '"':
960
+ return "&quot;";
961
+ default:
962
+ return c;
963
+ }
964
+ });
965
+ }
966
+
81
967
  // packages/cyrus-sdk/src/index.ts
82
968
  function createCyrusEmbedUrl(options = {}) {
83
- const host = options.hostUrl || "http://localhost:5173";
969
+ const host = (options.hostUrl || "http://localhost:5173").replace(/\/$/, "");
84
970
  const pid = options.projectId || "demo";
85
- const theme = options.theme || "light";
86
- return `${host}/#/embed/${pid}?theme=${theme}&readOnly=${Boolean(options.readOnly)}`;
971
+ const theme = options.theme || "dark";
972
+ const readOnly = Boolean(options.readOnly);
973
+ const queryParams = new URLSearchParams({
974
+ theme,
975
+ readOnly: String(readOnly)
976
+ });
977
+ if (options.whiteLabel) {
978
+ if (options.whiteLabel.brandName) queryParams.set("brand", options.whiteLabel.brandName);
979
+ if (options.whiteLabel.primaryColor) queryParams.set("primary", options.whiteLabel.primaryColor);
980
+ if (options.whiteLabel.logoUrl) queryParams.set("logo", options.whiteLabel.logoUrl);
981
+ if (options.whiteLabel.hideHeader) queryParams.set("hideHeader", "true");
982
+ if (options.whiteLabel.hideAIPanel) queryParams.set("hideAI", "true");
983
+ if (options.whiteLabel.hideProperties) queryParams.set("hideInspector", "true");
984
+ if (options.whiteLabel.hideCodeDrawer) queryParams.set("hideCode", "true");
985
+ }
986
+ return `${host}/#/embed/${pid}?${queryParams.toString()}`;
87
987
  }
988
+ var CyrusCanvasController = class {
989
+ iframeWindow = null;
990
+ targetOrigin = "*";
991
+ constructor(iframeWindow, targetOrigin = "*") {
992
+ this.iframeWindow = iframeWindow || null;
993
+ this.targetOrigin = targetOrigin;
994
+ }
995
+ setIframeWindow(win, origin = "*") {
996
+ this.iframeWindow = win;
997
+ this.targetOrigin = origin;
998
+ }
999
+ sendMessage(msg) {
1000
+ if (!this.iframeWindow) return;
1001
+ this.iframeWindow.postMessage(msg, this.targetOrigin);
1002
+ }
1003
+ setDocument(doc) {
1004
+ this.sendMessage({ type: "CYRUS_SET_DOCUMENT", payload: doc });
1005
+ }
1006
+ setTheme(theme) {
1007
+ this.sendMessage({ type: "CYRUS_SET_THEME", payload: { theme } });
1008
+ }
1009
+ selectElement(elementId) {
1010
+ this.sendMessage({ type: "CYRUS_SELECT_ELEMENT", payload: { elementId } });
1011
+ }
1012
+ createElement(element, select = true) {
1013
+ this.sendMessage({ type: "CYRUS_CREATE_ELEMENT", payload: { element, select } });
1014
+ }
1015
+ updateStyles(elementId, styles) {
1016
+ this.sendMessage({
1017
+ type: "CYRUS_UPDATE_ELEMENT",
1018
+ payload: { id: elementId, updates: { styles } }
1019
+ });
1020
+ }
1021
+ updateContent(elementId, content) {
1022
+ this.sendMessage({
1023
+ type: "CYRUS_UPDATE_ELEMENT",
1024
+ payload: { id: elementId, updates: { content } }
1025
+ });
1026
+ }
1027
+ deleteElement(elementId) {
1028
+ this.sendMessage({ type: "CYRUS_DELETE_ELEMENT", payload: { id: elementId } });
1029
+ }
1030
+ setDesignTokens(tokens) {
1031
+ this.sendMessage({ type: "CYRUS_SET_DESIGN_TOKENS", payload: tokens });
1032
+ }
1033
+ importTailwindTokens(tailwindConfig) {
1034
+ const tokens = importTailwindConfig(tailwindConfig);
1035
+ this.setDesignTokens(tokens);
1036
+ }
1037
+ importFigmaTokens(figmaTokensJson) {
1038
+ const tokens = parseFigmaTokens(figmaTokensJson);
1039
+ this.setDesignTokens(tokens);
1040
+ }
1041
+ registerComponent(definition) {
1042
+ this.sendMessage({ type: "CYRUS_REGISTER_CUSTOM_COMPONENT", payload: definition });
1043
+ }
1044
+ setWhiteLabelConfig(config) {
1045
+ this.sendMessage({ type: "CYRUS_SET_WHITELABEL_CONFIG", payload: config });
1046
+ }
1047
+ streamIncrementalElements(elements, isComplete = false) {
1048
+ this.sendMessage({
1049
+ type: "CYRUS_STREAM_INCREMENTAL_ELEMENTS",
1050
+ payload: { elements, isComplete }
1051
+ });
1052
+ }
1053
+ requestCanvasSnapshot(format = "svg") {
1054
+ this.sendMessage({ type: "CYRUS_REQUEST_CANVAS_SNAPSHOT", payload: { format } });
1055
+ }
1056
+ async executeMCPTool(toolCall) {
1057
+ return executeCyrusMCPTool(this, toolCall);
1058
+ }
1059
+ generateFromPrompt(prompt, provider, apiKey) {
1060
+ this.sendMessage({
1061
+ type: "CYRUS_GENERATE_FROM_PROMPT",
1062
+ payload: { prompt, provider, apiKey }
1063
+ });
1064
+ }
1065
+ };
88
1066
  function CyrusCanvasIframe({
89
1067
  projectId = "demo",
90
1068
  hostUrl = "http://localhost:5173",
91
- theme = "light",
1069
+ theme = "dark",
1070
+ initialDocument,
1071
+ whiteLabel,
1072
+ designTokens,
1073
+ customComponents,
1074
+ onReady,
92
1075
  onStateChange,
1076
+ onCodeSync,
1077
+ onSelectionChange,
1078
+ onMCPToolResult,
1079
+ controllerRef,
93
1080
  style,
94
1081
  className,
95
1082
  ...rest
96
1083
  }) {
97
- const embedUrl = createCyrusEmbedUrl({ projectId, hostUrl, theme });
1084
+ const iframeRef = import_react.default.useRef(null);
1085
+ const controllerInstance = import_react.default.useMemo(() => new CyrusCanvasController(null, "*"), []);
1086
+ const embedUrl = createCyrusEmbedUrl({
1087
+ projectId,
1088
+ hostUrl,
1089
+ theme,
1090
+ whiteLabel
1091
+ });
1092
+ import_react.default.useEffect(() => {
1093
+ if (iframeRef.current?.contentWindow) {
1094
+ controllerInstance.setIframeWindow(iframeRef.current.contentWindow, "*");
1095
+ }
1096
+ if (controllerRef) {
1097
+ controllerRef.current = controllerInstance;
1098
+ }
1099
+ }, [controllerInstance, controllerRef]);
98
1100
  import_react.default.useEffect(() => {
99
- if (!onStateChange || typeof window === "undefined") return;
1101
+ if (typeof window === "undefined") return;
100
1102
  const handleMessage = (event) => {
101
- if (event.data && event.data.type === "CYRUS_STATE_CHANGED") {
102
- onStateChange(event.data.payload);
1103
+ const data = event.data;
1104
+ if (!data || typeof data !== "object" || !data.type) return;
1105
+ switch (data.type) {
1106
+ case "CYRUS_READY":
1107
+ if (onReady) onReady(data.payload);
1108
+ if (initialDocument && iframeRef.current?.contentWindow) {
1109
+ controllerInstance.setDocument(validateAndRepairCyrusIR(initialDocument));
1110
+ }
1111
+ if (whiteLabel && iframeRef.current?.contentWindow) {
1112
+ controllerInstance.setWhiteLabelConfig(whiteLabel);
1113
+ }
1114
+ if (designTokens && iframeRef.current?.contentWindow) {
1115
+ controllerInstance.setDesignTokens(designTokens);
1116
+ }
1117
+ if (customComponents && Array.isArray(customComponents) && iframeRef.current?.contentWindow) {
1118
+ customComponents.forEach((comp) => controllerInstance.registerComponent(comp));
1119
+ }
1120
+ break;
1121
+ case "CYRUS_STATE_CHANGED":
1122
+ if (onStateChange) {
1123
+ const doc = "document" in data.payload ? data.payload.document : data.payload;
1124
+ onStateChange(doc);
1125
+ }
1126
+ break;
1127
+ case "CYRUS_CODE_SYNC":
1128
+ if (onCodeSync) onCodeSync(data.payload);
1129
+ break;
1130
+ case "CYRUS_SELECTION_CHANGED":
1131
+ if (onSelectionChange) onSelectionChange(data.payload.selectedIds);
1132
+ break;
1133
+ case "CYRUS_MCP_TOOL_RESULT":
1134
+ if (onMCPToolResult) onMCPToolResult(data.payload);
1135
+ break;
103
1136
  }
104
1137
  };
105
1138
  window.addEventListener("message", handleMessage);
106
1139
  return () => window.removeEventListener("message", handleMessage);
107
- }, [onStateChange]);
1140
+ }, [
1141
+ initialDocument,
1142
+ whiteLabel,
1143
+ designTokens,
1144
+ customComponents,
1145
+ onReady,
1146
+ onStateChange,
1147
+ onCodeSync,
1148
+ onSelectionChange,
1149
+ onMCPToolResult,
1150
+ controllerInstance
1151
+ ]);
108
1152
  return import_react.default.createElement("iframe", {
1153
+ ref: iframeRef,
109
1154
  src: embedUrl,
110
- className: className || "w-full h-full border-0 rounded-2xl shadow-lg",
1155
+ className: className || "w-full h-full border-0 rounded-2xl shadow-xl",
111
1156
  style: {
112
1157
  width: "100%",
113
- height: "600px",
1158
+ height: "750px",
114
1159
  border: "none",
115
1160
  borderRadius: "16px",
1161
+ backgroundColor: "#09090b",
116
1162
  ...style
117
1163
  },
118
- title: "Cyrus Visual Canvas Studio",
1164
+ title: whiteLabel?.brandName || "Cyrus Visual Canvas Studio",
119
1165
  ...rest
120
1166
  });
121
1167
  }