vibetachyon 1.8.0 → 2.0.0
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/mcp-server.js +412 -15
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -47,12 +47,150 @@ async function checkSanity() {
|
|
|
47
47
|
throw new Error(`[VIBETACHYON SANITY TRAP] Execution blocked. Agent exceeded ${CALL_LIMIT_PER_SESSION} tool calls in a single session. This prevents infinite loops or excessive API usage. Please restart the CLI if this was intentional.`);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
// --- Style Palettes — real CSS values per preset ─────────────────────────
|
|
51
|
+
const STYLE_PALETTES = {
|
|
52
|
+
linear: {
|
|
53
|
+
'--background': '#0a0a0f',
|
|
54
|
+
'--foreground': '#f4f4f5',
|
|
55
|
+
'--primary': '#6366f1',
|
|
56
|
+
'--primary-fg': '#ffffff',
|
|
57
|
+
'--muted': '#1c1c28',
|
|
58
|
+
'--muted-fg': '#8585a8',
|
|
59
|
+
'--border': 'rgba(255,255,255,0.08)',
|
|
60
|
+
'--card': '#111120',
|
|
61
|
+
'--radius': '8px',
|
|
62
|
+
'--font': "'Inter', system-ui, sans-serif",
|
|
63
|
+
'--gradient': 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
64
|
+
'--shadow': '0 1px 3px rgba(0,0,0,0.4)',
|
|
65
|
+
},
|
|
66
|
+
vercel: {
|
|
67
|
+
'--background': '#000000',
|
|
68
|
+
'--foreground': '#ffffff',
|
|
69
|
+
'--primary': '#ffffff',
|
|
70
|
+
'--primary-fg': '#000000',
|
|
71
|
+
'--muted': '#111111',
|
|
72
|
+
'--muted-fg': '#888888',
|
|
73
|
+
'--border': '#333333',
|
|
74
|
+
'--card': '#0a0a0a',
|
|
75
|
+
'--radius': '4px',
|
|
76
|
+
'--font': "'Geist', 'Inter', system-ui, sans-serif",
|
|
77
|
+
'--gradient': 'none',
|
|
78
|
+
'--shadow': '0 0 0 1px #333',
|
|
79
|
+
},
|
|
80
|
+
stripe: {
|
|
81
|
+
'--background': '#ffffff',
|
|
82
|
+
'--foreground': '#0a2540',
|
|
83
|
+
'--primary': '#635bff',
|
|
84
|
+
'--primary-fg': '#ffffff',
|
|
85
|
+
'--muted': '#f6f9fc',
|
|
86
|
+
'--muted-fg': '#425466',
|
|
87
|
+
'--border': '#e3e8ee',
|
|
88
|
+
'--card': '#ffffff',
|
|
89
|
+
'--radius': '6px',
|
|
90
|
+
'--font': "'Sohne', 'Inter', system-ui, sans-serif",
|
|
91
|
+
'--gradient': 'linear-gradient(135deg, #635bff 0%, #0a2540 100%)',
|
|
92
|
+
'--shadow': '0 2px 8px rgba(0,0,0,0.08)',
|
|
93
|
+
},
|
|
94
|
+
notion: {
|
|
95
|
+
'--background': '#ffffff',
|
|
96
|
+
'--foreground': '#37352f',
|
|
97
|
+
'--primary': '#2eaadc',
|
|
98
|
+
'--primary-fg': '#ffffff',
|
|
99
|
+
'--muted': '#f7f6f3',
|
|
100
|
+
'--muted-fg': '#9b9a97',
|
|
101
|
+
'--border': '#e9e9e7',
|
|
102
|
+
'--card': '#ffffff',
|
|
103
|
+
'--radius': '3px',
|
|
104
|
+
'--font': "'ui-sans-serif', 'Inter', system-ui, sans-serif",
|
|
105
|
+
'--gradient': 'none',
|
|
106
|
+
'--shadow': '0 1px 3px rgba(0,0,0,0.07)',
|
|
107
|
+
},
|
|
108
|
+
apple: {
|
|
109
|
+
'--background': '#f5f5f7',
|
|
110
|
+
'--foreground': '#1d1d1f',
|
|
111
|
+
'--primary': '#0066cc',
|
|
112
|
+
'--primary-fg': '#ffffff',
|
|
113
|
+
'--muted': '#e8e8ed',
|
|
114
|
+
'--muted-fg': '#6e6e73',
|
|
115
|
+
'--border': 'rgba(0,0,0,0.1)',
|
|
116
|
+
'--card': '#ffffff',
|
|
117
|
+
'--radius': '12px',
|
|
118
|
+
'--font': "'-apple-system', 'SF Pro Display', 'Inter', sans-serif",
|
|
119
|
+
'--gradient': 'linear-gradient(180deg, #f5f5f7 0%, #e8e8ed 100%)',
|
|
120
|
+
'--shadow': '0 4px 20px rgba(0,0,0,0.08)',
|
|
121
|
+
},
|
|
122
|
+
'bold-dark': {
|
|
123
|
+
'--background': '#060b18',
|
|
124
|
+
'--foreground': '#e2e8f0',
|
|
125
|
+
'--primary': '#06b6d4',
|
|
126
|
+
'--primary-fg': '#000000',
|
|
127
|
+
'--muted': '#0f1d2e',
|
|
128
|
+
'--muted-fg': '#94a3b8',
|
|
129
|
+
'--border': 'rgba(6,182,212,0.15)',
|
|
130
|
+
'--card': '#0d1a2e',
|
|
131
|
+
'--radius': '10px',
|
|
132
|
+
'--font': "'Inter', system-ui, sans-serif",
|
|
133
|
+
'--gradient': 'linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%)',
|
|
134
|
+
'--shadow': '0 4px 24px rgba(6,182,212,0.15)',
|
|
135
|
+
},
|
|
136
|
+
agency: {
|
|
137
|
+
'--background': '#0f0f0f',
|
|
138
|
+
'--foreground': '#ffffff',
|
|
139
|
+
'--primary': '#ff6b35',
|
|
140
|
+
'--primary-fg': '#ffffff',
|
|
141
|
+
'--muted': '#1a1a1a',
|
|
142
|
+
'--muted-fg': '#999999',
|
|
143
|
+
'--border': 'rgba(255,107,53,0.2)',
|
|
144
|
+
'--card': '#1a1a1a',
|
|
145
|
+
'--radius': '2px',
|
|
146
|
+
'--font': "'Space Grotesk', 'Inter', system-ui, sans-serif",
|
|
147
|
+
'--gradient': 'linear-gradient(135deg, #ff6b35 0%, #f7c59f 100%)',
|
|
148
|
+
'--shadow': '0 4px 20px rgba(255,107,53,0.2)',
|
|
149
|
+
},
|
|
150
|
+
aceternity: {
|
|
151
|
+
'--background': '#000000',
|
|
152
|
+
'--foreground': '#ffffff',
|
|
153
|
+
'--primary': '#a855f7',
|
|
154
|
+
'--primary-fg': '#ffffff',
|
|
155
|
+
'--muted': '#0a0a0a',
|
|
156
|
+
'--muted-fg': '#71717a',
|
|
157
|
+
'--border': 'rgba(168,85,247,0.2)',
|
|
158
|
+
'--card': '#0a0a0a',
|
|
159
|
+
'--radius': '16px',
|
|
160
|
+
'--font': "'Inter', system-ui, sans-serif",
|
|
161
|
+
'--gradient': 'linear-gradient(135deg, #a855f7 0%, #6366f1 50%, #ec4899 100%)',
|
|
162
|
+
'--shadow': '0 0 40px rgba(168,85,247,0.25)',
|
|
163
|
+
},
|
|
164
|
+
custom: {
|
|
165
|
+
'--background': 'var(--background, #ffffff)',
|
|
166
|
+
'--foreground': 'var(--foreground, #000000)',
|
|
167
|
+
'--primary': 'var(--primary, #3b82f6)',
|
|
168
|
+
'--primary-fg': 'var(--primary-fg, #ffffff)',
|
|
169
|
+
'--muted': 'var(--muted, #f4f4f5)',
|
|
170
|
+
'--muted-fg': 'var(--muted-fg, #71717a)',
|
|
171
|
+
'--border': 'var(--border, #e4e4e7)',
|
|
172
|
+
'--card': 'var(--card, #ffffff)',
|
|
173
|
+
'--radius': 'var(--radius, 8px)',
|
|
174
|
+
'--font': "var(--font-sans, 'Inter', sans-serif)",
|
|
175
|
+
'--gradient': 'none',
|
|
176
|
+
'--shadow': '0 1px 3px rgba(0,0,0,0.1)',
|
|
177
|
+
},
|
|
178
|
+
};
|
|
50
179
|
// --- Changelog (per version) ---
|
|
51
180
|
const CHANGELOG = {
|
|
181
|
+
'2.0.0': [
|
|
182
|
+
'8 presets visuais reais: linear, vercel, stripe, bold-dark, agency...',
|
|
183
|
+
'Paletas CSS aplicadas automaticamente — sem mais neon hardcoded',
|
|
184
|
+
'vibe_compose_landing_page lê o brief e injeta cores corretas',
|
|
185
|
+
],
|
|
186
|
+
'1.9.0': [
|
|
187
|
+
'vibe_clone_design — clone design system de qualquer URL',
|
|
188
|
+
'Detecta cores, tipografia, radius e sombras automaticamente',
|
|
189
|
+
'Gera globals.css pronto para usar no projeto',
|
|
190
|
+
],
|
|
52
191
|
'1.8.0': [
|
|
53
192
|
'vibe_session_recap — resumo completo da sessão',
|
|
54
193
|
'Update check automático no startup com changelog',
|
|
55
|
-
'vibe_clone_design — clone de design system por URL',
|
|
56
194
|
],
|
|
57
195
|
'1.7.0': [
|
|
58
196
|
'vibe_launchpad — MISSION BRIEFING automático antes de qualquer código',
|
|
@@ -199,7 +337,7 @@ async function validateTokenAtStartup() {
|
|
|
199
337
|
}
|
|
200
338
|
// --- Update Check ---
|
|
201
339
|
try {
|
|
202
|
-
const CURRENT_VERSION = '
|
|
340
|
+
const CURRENT_VERSION = '2.0.0'; // keep in sync with package.json
|
|
203
341
|
const npmRes = await fetch('https://registry.npmjs.org/vibetachyon/latest', {
|
|
204
342
|
signal: AbortSignal.timeout(4000)
|
|
205
343
|
});
|
|
@@ -228,7 +366,7 @@ async function startMcpServer() {
|
|
|
228
366
|
await validateTokenAtStartup();
|
|
229
367
|
const server = new mcp_js_1.McpServer({
|
|
230
368
|
name: "VibeTachyon MCP — Animmaster Engine",
|
|
231
|
-
version: "
|
|
369
|
+
version: "2.0.0"
|
|
232
370
|
});
|
|
233
371
|
/**
|
|
234
372
|
* VIBETACHYON FRONTEND PERSONA — Senior Frontend Designer (Animmaster Engine)
|
|
@@ -977,7 +1115,7 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
977
1115
|
niche: zod_1.z.string().describe("What is the product niche? e.g. 'B2B SaaS fintech', 'developer tool', 'e-commerce fashion'"),
|
|
978
1116
|
targetAudience: zod_1.z.string().describe("Who is the target user? e.g. 'CTO of early-stage startup', 'solo developer', 'non-technical founder'"),
|
|
979
1117
|
styleWords: zod_1.z.array(zod_1.z.string()).describe("3 adjectives that describe the desired visual style. e.g. ['clean', 'modern', 'trustworthy'] or ['bold', 'dark', 'animated']"),
|
|
980
|
-
referenceStyle: zod_1.z.enum(['linear', 'vercel', 'stripe', 'aceternity', 'apple', 'notion', 'custom']).describe("
|
|
1118
|
+
referenceStyle: zod_1.z.enum(['linear', 'vercel', 'stripe', 'aceternity', 'apple', 'notion', 'bold-dark', 'agency', 'custom']).describe("Visual preset: 'linear'=dark minimal indigo, 'vercel'=black/white terminal, 'stripe'=light professional, 'notion'=clean editorial, 'apple'=light cinematic, 'bold-dark'=dark navy cyan, 'agency'=dark orange dramatic, 'aceternity'=dark glowing purple, 'custom'=use project CSS vars"),
|
|
981
1119
|
sectionsNeeded: zod_1.z.array(zod_1.z.string()).describe("Which page sections are needed in order. e.g. ['hero', 'features', 'social-proof', 'pricing', 'faq', 'cta']")
|
|
982
1120
|
}, async ({ projectDir, pageGoal, niche, targetAudience, styleWords, referenceStyle, sectionsNeeded }) => {
|
|
983
1121
|
await checkSanity();
|
|
@@ -1060,6 +1198,10 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1060
1198
|
if (sectionsNeeded.includes('pricing') && !sectionsNeeded.includes('social-proof') && !sectionsNeeded.includes('testimonials')) {
|
|
1061
1199
|
sectionWarnings.push('⚠️ Pricing without social proof reduces trust — consider adding testimonials before pricing.');
|
|
1062
1200
|
}
|
|
1201
|
+
const palette = STYLE_PALETTES[referenceStyle] || STYLE_PALETTES.custom;
|
|
1202
|
+
const paletteCSS = Object.entries(palette)
|
|
1203
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
1204
|
+
.join('\n');
|
|
1063
1205
|
const brief = {
|
|
1064
1206
|
createdAt: new Date().toISOString(),
|
|
1065
1207
|
pageGoal,
|
|
@@ -1068,6 +1210,8 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1068
1210
|
styleWords,
|
|
1069
1211
|
referenceStyle,
|
|
1070
1212
|
sectionsNeeded,
|
|
1213
|
+
palette,
|
|
1214
|
+
paletteCSS: `:root {\n${paletteCSS}\n}`,
|
|
1071
1215
|
styleProfile: profile,
|
|
1072
1216
|
rules: {
|
|
1073
1217
|
maxAnimationTypes: profile.animationLevel === 'heavy' ? 3 : profile.animationLevel === 'subtle' ? 2 : 0,
|
|
@@ -1113,14 +1257,17 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1113
1257
|
`DESIGN RULES:\n${rulesText}\n\n` +
|
|
1114
1258
|
`SECTION SEQUENCE:\n${sectionsText}` +
|
|
1115
1259
|
`${warningsText}\n\n` +
|
|
1260
|
+
`COLOR PALETTE (USE THESE EXACT VALUES — NO EXCEPTIONS):\n` +
|
|
1261
|
+
`\`\`\`css\n${brief.paletteCSS}\n\`\`\`\n\n` +
|
|
1116
1262
|
`INSTRUCTION FOR AI — CRITICAL RULES FOR THIS SESSION:\n` +
|
|
1117
|
-
`1.
|
|
1118
|
-
`2.
|
|
1119
|
-
`3.
|
|
1120
|
-
`4.
|
|
1121
|
-
`5.
|
|
1122
|
-
`6.
|
|
1123
|
-
`7.
|
|
1263
|
+
`1. ALWAYS inject the CSS palette above as the FIRST thing inside <style> when generating HTML\n` +
|
|
1264
|
+
`2. NEVER hardcode colors like #7c3aed, #a855f7, #ec4899 — use ONLY the CSS vars above\n` +
|
|
1265
|
+
`3. Every component you choose MUST match the "${referenceStyle}" reference style\n` +
|
|
1266
|
+
`4. Visual tone is "${profile.tone}" — ${profile.tone === 'dark' ? 'dark backgrounds, light text' : profile.tone === 'light' ? 'light backgrounds, dark text' : 'adapt to project'}\n` +
|
|
1267
|
+
`5. MAXIMUM ${brief.rules.maxAnimationTypes} different animation styles on the entire page\n` +
|
|
1268
|
+
`6. NEVER use these: ${profile.avoid.join(', ')}\n` +
|
|
1269
|
+
`7. Build sections in this EXACT order: ${sectionsNeeded.join(' → ')}\n` +
|
|
1270
|
+
`8. If a component does NOT match this brief, REJECT it and search again`
|
|
1124
1271
|
}]
|
|
1125
1272
|
};
|
|
1126
1273
|
});
|
|
@@ -1188,6 +1335,221 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1188
1335
|
}
|
|
1189
1336
|
});
|
|
1190
1337
|
// Tool: Enforce Vibe Style (Consistency Guard)
|
|
1338
|
+
// ── Tool: Clone Design ───────────────────────────────────────────────────
|
|
1339
|
+
server.tool("vibe_clone_design", "🎨 Give a URL (e.g. linear.app, stripe.com, vercel.com) and get its complete design system cloned: colors, typography, border radius, spacing, shadows — all mapped to standard CSS variables and saved as globals.css. Use when the user says 'I want my site to look like X'.", {
|
|
1340
|
+
url: zod_1.z.string().describe("Full URL of the site to clone the design system from (e.g. 'https://linear.app')"),
|
|
1341
|
+
outputPath: zod_1.z.string().optional().describe("Absolute path where globals.css should be saved. If omitted, returns the CSS as text only."),
|
|
1342
|
+
mode: zod_1.z.enum(['full', 'colors-only', 'typography-only']).optional().describe("What to extract. Default: 'full'")
|
|
1343
|
+
}, async ({ url, outputPath, mode = 'full' }) => {
|
|
1344
|
+
await checkSanity();
|
|
1345
|
+
const lines = [];
|
|
1346
|
+
const sep = '═'.repeat(50);
|
|
1347
|
+
lines.push(`╔${sep}╗`);
|
|
1348
|
+
lines.push(`║ 🎨 DESIGN CLONE: ${new URL(url).hostname.padEnd(31)}║`);
|
|
1349
|
+
lines.push(`╠${sep}╣`);
|
|
1350
|
+
try {
|
|
1351
|
+
// Fetch HTML
|
|
1352
|
+
const htmlRes = await fetch(url, {
|
|
1353
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; VibeTachyon/1.0)' },
|
|
1354
|
+
signal: AbortSignal.timeout(10000)
|
|
1355
|
+
});
|
|
1356
|
+
const html = await htmlRes.text();
|
|
1357
|
+
const baseUrl = new URL(url).origin;
|
|
1358
|
+
// Collect CSS sources: <style> tags + <link rel="stylesheet">
|
|
1359
|
+
let allCss = '';
|
|
1360
|
+
// Inline styles
|
|
1361
|
+
const styleMatches = html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
|
1362
|
+
for (const m of styleMatches)
|
|
1363
|
+
allCss += m[1] + '\n';
|
|
1364
|
+
// External stylesheets (fetch first 3 to stay fast)
|
|
1365
|
+
const linkMatches = [...html.matchAll(/<link[^>]+rel=["']stylesheet["'][^>]*href=["']([^"']+)["']/gi)];
|
|
1366
|
+
const cssUrls = linkMatches.map(m => {
|
|
1367
|
+
const href = m[1];
|
|
1368
|
+
if (href.startsWith('http'))
|
|
1369
|
+
return href;
|
|
1370
|
+
if (href.startsWith('//'))
|
|
1371
|
+
return 'https:' + href;
|
|
1372
|
+
return baseUrl + (href.startsWith('/') ? href : '/' + href);
|
|
1373
|
+
}).slice(0, 4);
|
|
1374
|
+
await Promise.allSettled(cssUrls.map(async (cssUrl) => {
|
|
1375
|
+
try {
|
|
1376
|
+
const r = await fetch(cssUrl, { signal: AbortSignal.timeout(5000) });
|
|
1377
|
+
allCss += await r.text() + '\n';
|
|
1378
|
+
}
|
|
1379
|
+
catch { /* skip failed CSS */ }
|
|
1380
|
+
}));
|
|
1381
|
+
// ── Extract design tokens ──────────────────────────────────
|
|
1382
|
+
// 1. CSS Custom Properties from :root
|
|
1383
|
+
const rootVars = {};
|
|
1384
|
+
const rootBlocks = allCss.matchAll(/:root\s*\{([^}]+)\}/g);
|
|
1385
|
+
for (const block of rootBlocks) {
|
|
1386
|
+
const varMatches = block[1].matchAll(/--([\w-]+)\s*:\s*([^;]+);/g);
|
|
1387
|
+
for (const v of varMatches)
|
|
1388
|
+
rootVars[v[1]] = v[2].trim();
|
|
1389
|
+
}
|
|
1390
|
+
// 2. Font families
|
|
1391
|
+
const fontMatches = allCss.matchAll(/font-family\s*:\s*([^;}{]+)/g);
|
|
1392
|
+
const fonts = [];
|
|
1393
|
+
for (const m of fontMatches) {
|
|
1394
|
+
const f = m[1].trim().split(',')[0].replace(/['"]/g, '').trim();
|
|
1395
|
+
if (f && !fonts.includes(f) && f !== 'inherit' && f !== 'sans-serif')
|
|
1396
|
+
fonts.push(f);
|
|
1397
|
+
}
|
|
1398
|
+
// Also check Google Fonts links
|
|
1399
|
+
const gFontMatch = html.match(/fonts\.googleapis\.com[^"']*family=([^&"']+)/);
|
|
1400
|
+
if (gFontMatch) {
|
|
1401
|
+
const gFont = decodeURIComponent(gFontMatch[1]).split(':')[0].replace(/\+/g, ' ');
|
|
1402
|
+
if (!fonts.includes(gFont))
|
|
1403
|
+
fonts.unshift(gFont);
|
|
1404
|
+
}
|
|
1405
|
+
// 3. Border radius (from buttons/cards)
|
|
1406
|
+
const radiusMatches = allCss.matchAll(/border-radius\s*:\s*([\d.]+(?:px|rem|em))/g);
|
|
1407
|
+
const radii = [];
|
|
1408
|
+
for (const m of radiusMatches) {
|
|
1409
|
+
const val = parseFloat(m[1]);
|
|
1410
|
+
if (val > 0 && val <= 32)
|
|
1411
|
+
radii.push(val);
|
|
1412
|
+
}
|
|
1413
|
+
const medianRadius = radii.length > 0
|
|
1414
|
+
? radii.sort((a, b) => a - b)[Math.floor(radii.length / 2)]
|
|
1415
|
+
: 6;
|
|
1416
|
+
const radiusUnit = allCss.includes('rem') ? 'rem' : 'px';
|
|
1417
|
+
const radiusVal = radiusUnit === 'rem' ? `${(medianRadius / 16).toFixed(3)}rem` : `${medianRadius}px`;
|
|
1418
|
+
// 4. Colors — find dominant non-white/black hex colors
|
|
1419
|
+
const hexColors = [...new Set([...allCss.matchAll(/#([0-9a-fA-F]{6})\b/g)].map(m => '#' + m[1].toLowerCase()))];
|
|
1420
|
+
const isNeutral = (hex) => {
|
|
1421
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
1422
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
1423
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
1424
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
1425
|
+
return (max - min) < 30; // low saturation = neutral
|
|
1426
|
+
};
|
|
1427
|
+
const brandColors = hexColors.filter(h => !isNeutral(h)).slice(0, 5);
|
|
1428
|
+
const darkColors = hexColors.filter(h => {
|
|
1429
|
+
const r = parseInt(h.slice(1, 3), 16);
|
|
1430
|
+
return r < 30 && isNeutral(h);
|
|
1431
|
+
});
|
|
1432
|
+
const lightColors = hexColors.filter(h => {
|
|
1433
|
+
const r = parseInt(h.slice(1, 3), 16);
|
|
1434
|
+
return r > 225 && isNeutral(h);
|
|
1435
|
+
});
|
|
1436
|
+
// 5. Detect dark/light theme from background
|
|
1437
|
+
const bgColorMatch = allCss.match(/body\s*\{[^}]*background(?:-color)?\s*:\s*(#[0-9a-fA-F]{6}|[a-z]+)/);
|
|
1438
|
+
const bgHex = bgColorMatch?.[1] || (darkColors[0] ?? '#ffffff');
|
|
1439
|
+
const isDark = bgHex.startsWith('#') ? parseInt(bgHex.slice(1, 3), 16) < 100 : bgHex === 'black';
|
|
1440
|
+
// 6. Detect shadow style
|
|
1441
|
+
const hasShadow = allCss.includes('box-shadow');
|
|
1442
|
+
const shadowVal = hasShadow
|
|
1443
|
+
? (isDark ? '0 1px 3px rgba(0,0,0,0.4)' : '0 1px 3px rgba(0,0,0,0.1)')
|
|
1444
|
+
: 'none';
|
|
1445
|
+
// ── Map to standard tokens ─────────────────────────────────
|
|
1446
|
+
const primary = brandColors[0] || (isDark ? '#6366f1' : '#3b82f6');
|
|
1447
|
+
const primaryFg = '#ffffff';
|
|
1448
|
+
const background = isDark ? (darkColors[0] || '#0a0a0a') : (lightColors[0] || '#ffffff');
|
|
1449
|
+
const foreground = isDark ? '#f4f4f5' : '#09090b';
|
|
1450
|
+
const muted = isDark ? '#27272a' : '#f4f4f5';
|
|
1451
|
+
const mutedFg = isDark ? '#a1a1aa' : '#71717a';
|
|
1452
|
+
const border = isDark ? '#3f3f46' : '#e4e4e7';
|
|
1453
|
+
const card = isDark ? (darkColors[1] || '#18181b') : '#ffffff';
|
|
1454
|
+
const primaryFont = fonts[0] || (isDark ? 'Inter' : 'Inter');
|
|
1455
|
+
const monoFont = fonts.find(f => f.toLowerCase().includes('mono') || f.toLowerCase().includes('code')) || 'JetBrains Mono';
|
|
1456
|
+
// ── Detect visual style for Animmaster brief ──────────────
|
|
1457
|
+
const hasGradients = allCss.includes('linear-gradient') || allCss.includes('radial-gradient');
|
|
1458
|
+
const hasBlur = allCss.includes('backdrop-filter') || allCss.includes('blur(');
|
|
1459
|
+
const visualTone = isDark && hasBlur ? 'glassmorphism' :
|
|
1460
|
+
isDark && hasGradients ? 'dark-gradient' :
|
|
1461
|
+
isDark ? 'dark-minimal' :
|
|
1462
|
+
hasGradients ? 'light-gradient' : 'light-minimal';
|
|
1463
|
+
// ── Use existing root vars if abundant ────────────────────
|
|
1464
|
+
const hasRichVars = Object.keys(rootVars).length > 8;
|
|
1465
|
+
// ── Generate globals.css ───────────────────────────────────
|
|
1466
|
+
const cssOutput = [
|
|
1467
|
+
`/* ─────────────────────────────────────────────────────`,
|
|
1468
|
+
` * Design System cloned from: ${new URL(url).hostname}`,
|
|
1469
|
+
` * Generated by VibeTachyon vibe_clone_design`,
|
|
1470
|
+
` * Visual tone: ${visualTone}`,
|
|
1471
|
+
` * ──────────────────────────────────────────────────── */`,
|
|
1472
|
+
'',
|
|
1473
|
+
hasRichVars
|
|
1474
|
+
? `/* Original CSS variables preserved from source */\n:root {\n${Object.entries(rootVars).slice(0, 30).map(([k, v]) => ` --${k}: ${v};`).join('\n')}\n}`
|
|
1475
|
+
: '',
|
|
1476
|
+
'',
|
|
1477
|
+
`/* VibeTachyon Standard Tokens */`,
|
|
1478
|
+
`:root {`,
|
|
1479
|
+
` /* Colors */`,
|
|
1480
|
+
` --background: ${background};`,
|
|
1481
|
+
` --foreground: ${foreground};`,
|
|
1482
|
+
` --card: ${card};`,
|
|
1483
|
+
` --card-fg: ${foreground};`,
|
|
1484
|
+
` --primary: ${primary};`,
|
|
1485
|
+
` --primary-fg: ${primaryFg};`,
|
|
1486
|
+
` --muted: ${muted};`,
|
|
1487
|
+
` --muted-fg: ${mutedFg};`,
|
|
1488
|
+
` --border: ${border};`,
|
|
1489
|
+
` --input: ${border};`,
|
|
1490
|
+
` --ring: ${primary};`,
|
|
1491
|
+
...(brandColors.slice(1).map((c, i) => ` --accent-${i + 1}: ${c};`)),
|
|
1492
|
+
'',
|
|
1493
|
+
` /* Typography */`,
|
|
1494
|
+
` --font-sans: '${primaryFont}', system-ui, sans-serif;`,
|
|
1495
|
+
` --font-mono: '${monoFont}', monospace;`,
|
|
1496
|
+
'',
|
|
1497
|
+
` /* Shape */`,
|
|
1498
|
+
` --radius: ${radiusVal};`,
|
|
1499
|
+
` --radius-sm: calc(var(--radius) * 0.5);`,
|
|
1500
|
+
` --radius-lg: calc(var(--radius) * 1.5);`,
|
|
1501
|
+
'',
|
|
1502
|
+
` /* Shadows */`,
|
|
1503
|
+
` --shadow-sm: ${shadowVal};`,
|
|
1504
|
+
` --shadow: ${shadowVal.replace('3px', '8px').replace('0.1', '0.12').replace('0.4', '0.5')};`,
|
|
1505
|
+
`}`,
|
|
1506
|
+
'',
|
|
1507
|
+
isDark ? [
|
|
1508
|
+
`.dark {`,
|
|
1509
|
+
` color-scheme: dark;`,
|
|
1510
|
+
`}`,
|
|
1511
|
+
].join('\n') : '',
|
|
1512
|
+
].filter(Boolean).join('\n');
|
|
1513
|
+
// Save to file if requested
|
|
1514
|
+
if (outputPath) {
|
|
1515
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(outputPath));
|
|
1516
|
+
await fs_extra_1.default.writeFile(outputPath, cssOutput, 'utf-8');
|
|
1517
|
+
}
|
|
1518
|
+
// Build briefing output
|
|
1519
|
+
lines.push(` Theme : ${isDark ? '🌑 Dark' : '☀️ Light'} (${visualTone})`);
|
|
1520
|
+
lines.push(` Primary : ${primary}`);
|
|
1521
|
+
lines.push(` BG : ${background}`);
|
|
1522
|
+
lines.push(` FG : ${foreground}`);
|
|
1523
|
+
lines.push(` Border : ${border}`);
|
|
1524
|
+
lines.push(` Radius : ${radiusVal}`);
|
|
1525
|
+
lines.push(` Font : ${primaryFont}`);
|
|
1526
|
+
if (fonts[1])
|
|
1527
|
+
lines.push(` Font 2 : ${fonts[1]}`);
|
|
1528
|
+
if (brandColors.length > 1)
|
|
1529
|
+
lines.push(` Accents : ${brandColors.slice(1, 4).join(', ')}`);
|
|
1530
|
+
lines.push(` Vars src : ${hasRichVars ? `✅ ${Object.keys(rootVars).length} CSS vars found` : '⚙️ Generated from computed styles'}`);
|
|
1531
|
+
lines.push(`╠${sep}╣`);
|
|
1532
|
+
lines.push(` Animmaster tone: ${visualTone}`);
|
|
1533
|
+
lines.push(` Use in brief : visualTone: "${visualTone}"`);
|
|
1534
|
+
lines.push(`╠${sep}╣`);
|
|
1535
|
+
if (outputPath) {
|
|
1536
|
+
lines.push(` ✅ globals.css saved to:`);
|
|
1537
|
+
lines.push(` ${outputPath}`);
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
lines.push(` 💡 Pass outputPath to save globals.css automatically`);
|
|
1541
|
+
}
|
|
1542
|
+
lines.push(`╚${sep}╝`);
|
|
1543
|
+
const fullOutput = lines.join('\n') + '\n\n' + '```css\n' + cssOutput + '\n```';
|
|
1544
|
+
return { content: [{ type: "text", text: fullOutput }] };
|
|
1545
|
+
}
|
|
1546
|
+
catch (err) {
|
|
1547
|
+
lines.push(` ❌ Erro: ${err.message}`);
|
|
1548
|
+
lines.push(` Verifique se a URL está acessível e tente novamente.`);
|
|
1549
|
+
lines.push(`╚${sep}╝`);
|
|
1550
|
+
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1191
1553
|
server.tool("vibe_enforce_style", "Passive audit tool that scan a code snippet and automatically patches 'off-theme' Tailwind classes (like bg-blue-500) with project-specific design tokens (--primary, --background) extracted from the CSS.", {
|
|
1192
1554
|
code: zod_1.z.string().describe("The TSX/JSX code snippet to audit and fix."),
|
|
1193
1555
|
cssFilePath: zod_1.z.string().describe("Absolute path to the user's globals.css.")
|
|
@@ -1347,11 +1709,39 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1347
1709
|
}
|
|
1348
1710
|
});
|
|
1349
1711
|
// Tool: Compose Landing Page
|
|
1350
|
-
server.tool("vibe_compose_landing_page", "Macro-composer that autonomously queries the VibeCodes RAG database for a full Landing Page structure (Navbar, Hero, Features, Pricing, Footer).", {
|
|
1351
|
-
themeOrIndustry: zod_1.z.string().describe("The theme, industry, or style of the page (e.g., 'SaaS Finance
|
|
1352
|
-
|
|
1712
|
+
server.tool("vibe_compose_landing_page", "Macro-composer that autonomously queries the VibeCodes RAG database for a full Landing Page structure (Navbar, Hero, Features, Pricing, Footer). ALWAYS call vibe_design_brief first — this tool reads the saved brief to apply the correct color palette.", {
|
|
1713
|
+
themeOrIndustry: zod_1.z.string().describe("The theme, industry, or style of the page (e.g., 'SaaS Finance', 'Dental Clinic', 'Developer Tool')"),
|
|
1714
|
+
projectDir: zod_1.z.string().optional().describe("Absolute path to the project root. Used to read the design brief.")
|
|
1715
|
+
}, async ({ themeOrIndustry, projectDir }) => {
|
|
1353
1716
|
await checkSanity();
|
|
1354
1717
|
try {
|
|
1718
|
+
// Read design brief for palette injection
|
|
1719
|
+
let paletteCSS = '';
|
|
1720
|
+
let briefStyle = 'linear';
|
|
1721
|
+
let briefTone = 'dark';
|
|
1722
|
+
let briefAvoid = [];
|
|
1723
|
+
const cwd = projectDir || process.cwd();
|
|
1724
|
+
const root = await findProjectRoot(cwd);
|
|
1725
|
+
const briefPath = path_1.default.join(root, '.vibetachyon', 'design-session.json');
|
|
1726
|
+
if (await fs_extra_1.default.pathExists(briefPath)) {
|
|
1727
|
+
try {
|
|
1728
|
+
const brief = await fs_extra_1.default.readJson(briefPath);
|
|
1729
|
+
if (brief.paletteCSS)
|
|
1730
|
+
paletteCSS = brief.paletteCSS;
|
|
1731
|
+
if (brief.referenceStyle)
|
|
1732
|
+
briefStyle = brief.referenceStyle;
|
|
1733
|
+
if (brief.styleProfile?.tone)
|
|
1734
|
+
briefTone = brief.styleProfile.tone;
|
|
1735
|
+
if (brief.rules?.mustAvoid)
|
|
1736
|
+
briefAvoid = brief.rules.mustAvoid;
|
|
1737
|
+
}
|
|
1738
|
+
catch { /* use defaults */ }
|
|
1739
|
+
}
|
|
1740
|
+
// Fallback: use linear palette if no brief
|
|
1741
|
+
if (!paletteCSS) {
|
|
1742
|
+
const fallbackPalette = STYLE_PALETTES[briefStyle] || STYLE_PALETTES.linear;
|
|
1743
|
+
paletteCSS = `:root {\n${Object.entries(fallbackPalette).map(([k, v]) => ` ${k}: ${v};`).join('\n')}\n}`;
|
|
1744
|
+
}
|
|
1355
1745
|
// Map sections to Animmaster use cases and search terms
|
|
1356
1746
|
const sectionMap = [
|
|
1357
1747
|
{ section: 'preloader', query: 'preloader', useCase: 'preloader' },
|
|
@@ -1361,7 +1751,14 @@ COMPONENT SELECTION FILTER (apply mentally before using any component):
|
|
|
1361
1751
|
{ section: 'features', query: 'features section', useCase: 'feature-section' },
|
|
1362
1752
|
{ section: 'footer', query: 'footer', useCase: 'footer' }
|
|
1363
1753
|
];
|
|
1364
|
-
let combinedPage = `[🎬 ANIMMASTER PAGE COMPOSER: ${themeOrIndustry}]\n
|
|
1754
|
+
let combinedPage = `[🎬 ANIMMASTER PAGE COMPOSER: ${themeOrIndustry}]\n`;
|
|
1755
|
+
combinedPage += `Style: ${briefStyle} | Tone: ${briefTone}\n\n`;
|
|
1756
|
+
combinedPage += `⚠️ MANDATORY COLOR RULES — INJECT THIS PALETTE FIRST IN <style>:\n\`\`\`css\n${paletteCSS}\n\`\`\`\n\n`;
|
|
1757
|
+
combinedPage += `CRITICAL: Use ONLY var(--background), var(--primary), var(--foreground), var(--border), var(--card) etc.\n`;
|
|
1758
|
+
combinedPage += `NEVER hardcode hex colors. NEVER use purple/neon unless --primary IS purple in the palette above.\n`;
|
|
1759
|
+
if (briefAvoid.length > 0)
|
|
1760
|
+
combinedPage += `AVOID: ${briefAvoid.join(', ')}\n`;
|
|
1761
|
+
combinedPage += `\nINSTRUCTION FOR AI: Assemble a complete page using Animmaster components in init_order sequence.\n\n`;
|
|
1365
1762
|
const allComponents = [];
|
|
1366
1763
|
for (const { section, query, useCase } of sectionMap) {
|
|
1367
1764
|
const { data } = await supabase
|