ebade 0.4.3 โ 0.4.6
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/.agent/workflows/prompt-to-product.md +40 -0
- package/CHANGELOG.md +43 -0
- package/cli/scaffold.js +481 -142
- package/package.json +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/index.ts +41 -2
- package/packages/mcp-server/src/tools/build.ts +57 -0
- package/tests/cli/build.test.js +66 -0
- package/tests/framework/architect.test.js +102 -0
- package/www/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: ebade Prompt-to-Product Workflow
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# ๐ฃ ebade Prompt-to-Product Workflow
|
|
6
|
+
|
|
7
|
+
This workflow describes how an AI Agent should use the ebade framework to transform a user's natural language intent into a production-ready codebase.
|
|
8
|
+
|
|
9
|
+
## 1. Initial Project Scaffolding
|
|
10
|
+
When a user asks to "build", "create", or "start" a new web application:
|
|
11
|
+
|
|
12
|
+
// turbo
|
|
13
|
+
1. Use the `ebade build` command with the user's prompt.
|
|
14
|
+
```bash
|
|
15
|
+
node cli/scaffold.js build "[User's Prompt]"
|
|
16
|
+
```
|
|
17
|
+
*This tool will automatically choose the project type, color palette, and initial components.*
|
|
18
|
+
|
|
19
|
+
2. Navigate into the generated project directory.
|
|
20
|
+
|
|
21
|
+
## 2. Understanding the Architecture
|
|
22
|
+
ebade generates a `project.ebade.yaml` file in the project root. This is your **Source of Truth**.
|
|
23
|
+
1. Read `project.ebade.yaml` to understand the pages, components, and data models.
|
|
24
|
+
2. Respect the design system tokens in `app/globals.css`.
|
|
25
|
+
|
|
26
|
+
## 3. Iterative Development
|
|
27
|
+
Once the scaffold is ready, move from "Architect" to "Engineer":
|
|
28
|
+
|
|
29
|
+
1. **Refine Components**: Locate generated components in `components/`. They are high-quality visual placeholders. Add the specific business logic, API calls, or complex state management requested by the user.
|
|
30
|
+
2. **Implement API Logic**: Fill the `app/api/` route handlers with real backend logic (database queries, external service integrations).
|
|
31
|
+
3. **Data Schemas**: Use the generated SQL in `database/schema.sql` to set up the actual database.
|
|
32
|
+
|
|
33
|
+
## 4. Expanding the Project
|
|
34
|
+
To add new features:
|
|
35
|
+
1. Update `project.ebade.yaml` with the new intent.
|
|
36
|
+
2. Run `node cli/scaffold.js scaffold project.ebade.yaml .` to generate the new files while preserving your logic.
|
|
37
|
+
3. Apply the specific implementation details to the new files.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
*Built for the Agent-First Era. ebade empowers you to focus on logic, while we handle the architecture.*
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,49 @@ All notable changes to **ebade** will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.6] - 2025-01-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **EbadeArchitect (The Brain)**: New core engine that translates natural language prompts into structured `ebade` configurations.
|
|
13
|
+
- **`ebade build <prompt>`**: A "one-shot" project creation command. Designs, scaffolds, and prepares a project in seconds.
|
|
14
|
+
- **Architect Intelligence**: Projects now gain context-aware page structures (E-commerce get cart/products, Blog get posts, etc.) and smarter component selection via Regex.
|
|
15
|
+
- **Auto-Environment**: Automatically generates `.gitignore` and `.env.example` for professional project starts.
|
|
16
|
+
- **Agent Workflow Documentation**: Added `.agent/workflows/prompt-to-product.md` to guide AI agents on using the new tools.
|
|
17
|
+
- **MCP Server v0.4.6**: Integrated `ebade_build` as a tool for otonomous agent usage.
|
|
18
|
+
- **Testing Layer**: Added internal testing suites for both the framework logic and CLI integration.
|
|
19
|
+
- **CLI Branding**: Updated to `v0.4.6` with improved help menus and creative briefs.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed CLI entry point to allow safe imports of `scaffold.js` without triggering help output.
|
|
24
|
+
- Improved project naming logic to filter out filler words (e.g., "Can you make a...").
|
|
25
|
+
- Fixed component integrity check to correctly locate test files in `tests/components/`.
|
|
26
|
+
|
|
27
|
+
## [0.4.5] - 2025-01-10
|
|
28
|
+
...
|
|
29
|
+
## [0.4.5] - 2025-01-10
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- UI/UX Overhaul: Premium dark-mode aesthetic with ambient glow and glassmorphism by default.
|
|
34
|
+
- Dynamic Color Support: Real-time Hex-to-HSL conversion to apply user's primary color choice to CSS variables.
|
|
35
|
+
- Component "Intents": Placeholders are now high-quality "Glass Cards" that look like part of a finished UI.
|
|
36
|
+
- Test Organization: All unit tests are now generated in a centralized `tests/` directory instead of being cluttered with components.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- API Pathing: Fixed a bug that caused double-nesting (e.g., `/api/api/...`) in generated routes.
|
|
41
|
+
- Removed debug information (headers, route labels) from generated pages for a "turnkey" production feel.
|
|
42
|
+
|
|
43
|
+
## [0.4.4] - 2025-01-10
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- Added `postcss.config.js` generation to enable Tailwind styling in scaffolded projects.
|
|
48
|
+
- Fixed double-nesting issues when initializing projects (removed redundant `projectName/projectName` folder creation).
|
|
49
|
+
- Refined Tailwind config to ensure all component paths are correctly watched.
|
|
50
|
+
|
|
8
51
|
## [0.4.3] - 2025-01-10
|
|
9
52
|
|
|
10
53
|
### Fixed
|
package/cli/scaffold.js
CHANGED
|
@@ -44,7 +44,7 @@ ${colors.magenta} โโโโโโ ${colors.cyan}โโโโโโโโ$
|
|
|
44
44
|
${colors.magenta} โโโโโโโโ${colors.cyan}โโโโโโโโ${colors.magenta}โโโ โโโ${colors.cyan}โโโโโโโโ${colors.magenta}โโโโโโโโ
|
|
45
45
|
${colors.magenta} โโโโโโโโ${colors.cyan}โโโโโโโ ${colors.magenta}โโโ โโโ${colors.cyan}โโโโโโโ ${colors.magenta}โโโโโโโโ${colors.reset}
|
|
46
46
|
|
|
47
|
-
${colors.dim}โจ Agent-First Framework ${colors.yellow}v0.4.
|
|
47
|
+
${colors.dim}โจ Agent-First Framework ${colors.yellow}v0.4.6${colors.reset}
|
|
48
48
|
`;
|
|
49
49
|
|
|
50
50
|
const log = {
|
|
@@ -58,6 +58,166 @@ const log = {
|
|
|
58
58
|
console.log(`\n${colors.bright}${colors.magenta}โธ ${msg}${colors.reset}`),
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
// ============================================
|
|
62
|
+
// ebade Architect (The Brain)
|
|
63
|
+
// ============================================
|
|
64
|
+
export class EbadeArchitect {
|
|
65
|
+
static async plan(prompt) {
|
|
66
|
+
const p = prompt.toLowerCase();
|
|
67
|
+
|
|
68
|
+
// Core App Type Detection
|
|
69
|
+
let type = "saas-dashboard";
|
|
70
|
+
if (p.includes("e-commerce") || p.includes("shop") || p.includes("store"))
|
|
71
|
+
type = "e-commerce";
|
|
72
|
+
if (p.includes("blog") || p.includes("article") || p.includes("news"))
|
|
73
|
+
type = "blog";
|
|
74
|
+
if (p.includes("portfolio") || p.includes("personal") || p.includes("cv"))
|
|
75
|
+
type = "portfolio";
|
|
76
|
+
|
|
77
|
+
// Component Intelligence
|
|
78
|
+
const components = ["navbar", "footer"];
|
|
79
|
+
const features = [];
|
|
80
|
+
|
|
81
|
+
const has = (keyword) => new RegExp(`\\b${keyword}`, "i").test(p);
|
|
82
|
+
|
|
83
|
+
if (has("chart") || has("analytics") || has("graph") || has("metric")) {
|
|
84
|
+
components.push("activity-chart", "stats-grid");
|
|
85
|
+
features.push("Advanced Analytics");
|
|
86
|
+
}
|
|
87
|
+
if (has("saas") || has("dashboard") || has("admin")) {
|
|
88
|
+
components.push("sidebar-navigation", "metrics-cards");
|
|
89
|
+
features.push("Admin Dashboard");
|
|
90
|
+
}
|
|
91
|
+
if (has("login") || has("auth") || has("sign") || has("user")) {
|
|
92
|
+
components.push("login-form", "signup-form");
|
|
93
|
+
features.push("Authentication");
|
|
94
|
+
}
|
|
95
|
+
if (has("price") || has("plan") || has("subscribe") || has("billing")) {
|
|
96
|
+
components.push("pricing-table", "cta-banner");
|
|
97
|
+
features.push("Subscription Tiers");
|
|
98
|
+
}
|
|
99
|
+
if (has("testim") || has("review") || has("social proof"))
|
|
100
|
+
components.push("testimonials-grid");
|
|
101
|
+
if (has("contact") || has("form") || has("help") || has("support"))
|
|
102
|
+
components.push("contact-form");
|
|
103
|
+
if (has("faq") || has("question")) components.push("faq-accordion");
|
|
104
|
+
|
|
105
|
+
// Dynamic Color Palette (Order matters - specific colors should win over vibes)
|
|
106
|
+
let primary = "#6366f1"; // Indigo default
|
|
107
|
+
if (has("gold") || has("luxury") || has("premium") || has("exclusive"))
|
|
108
|
+
primary = "#fbbf24";
|
|
109
|
+
if (has("green") || has("eco") || has("emerald") || has("nature"))
|
|
110
|
+
primary = "#10b981";
|
|
111
|
+
if (
|
|
112
|
+
has("blue") ||
|
|
113
|
+
has("ocean") ||
|
|
114
|
+
has("sky") ||
|
|
115
|
+
has("trust") ||
|
|
116
|
+
has("corp")
|
|
117
|
+
)
|
|
118
|
+
primary = "#3b82f6";
|
|
119
|
+
if (has("violet") || has("purple") || has("creative") || has("design"))
|
|
120
|
+
primary = "#8b5cf6";
|
|
121
|
+
if (has("orange") || has("fire") || has("warm") || has("brand"))
|
|
122
|
+
primary = "#f59e0b";
|
|
123
|
+
if (has("red") || has("danger") || has("hot") || has("love"))
|
|
124
|
+
primary = "#ef4444";
|
|
125
|
+
|
|
126
|
+
// Smart Pages based on Type
|
|
127
|
+
const pages = [
|
|
128
|
+
{
|
|
129
|
+
path: "/",
|
|
130
|
+
intent: "landing-page",
|
|
131
|
+
components: components.filter((c) =>
|
|
132
|
+
[
|
|
133
|
+
"navbar",
|
|
134
|
+
"hero-section",
|
|
135
|
+
"pricing-table",
|
|
136
|
+
"testimonials-grid",
|
|
137
|
+
"cta-banner",
|
|
138
|
+
"footer",
|
|
139
|
+
].includes(c)
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (type === "saas-dashboard") {
|
|
145
|
+
pages.push({
|
|
146
|
+
path: "/dashboard",
|
|
147
|
+
intent: "main-dashboard",
|
|
148
|
+
components: components.filter((c) =>
|
|
149
|
+
[
|
|
150
|
+
"sidebar-navigation",
|
|
151
|
+
"stats-grid",
|
|
152
|
+
"activity-chart",
|
|
153
|
+
"metrics-cards",
|
|
154
|
+
].includes(c)
|
|
155
|
+
),
|
|
156
|
+
});
|
|
157
|
+
} else if (type === "e-commerce") {
|
|
158
|
+
pages.push(
|
|
159
|
+
{
|
|
160
|
+
path: "/products",
|
|
161
|
+
intent: "product-list",
|
|
162
|
+
components: ["product-grid"],
|
|
163
|
+
},
|
|
164
|
+
{ path: "/cart", intent: "shopping-cart", components: ["cart-list"] }
|
|
165
|
+
);
|
|
166
|
+
} else if (type === "blog") {
|
|
167
|
+
pages.push(
|
|
168
|
+
{ path: "/posts", intent: "blog-index", components: ["post-list"] },
|
|
169
|
+
{
|
|
170
|
+
path: "/posts/[slug]",
|
|
171
|
+
intent: "blog-post",
|
|
172
|
+
components: ["post-body"],
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Build Internal Intent
|
|
178
|
+
const config = {
|
|
179
|
+
name:
|
|
180
|
+
p
|
|
181
|
+
.split(" ")
|
|
182
|
+
.filter(
|
|
183
|
+
(w) =>
|
|
184
|
+
![
|
|
185
|
+
"can",
|
|
186
|
+
"you",
|
|
187
|
+
"make",
|
|
188
|
+
"create",
|
|
189
|
+
"a",
|
|
190
|
+
"an",
|
|
191
|
+
"the",
|
|
192
|
+
"with",
|
|
193
|
+
"please",
|
|
194
|
+
"super",
|
|
195
|
+
"ultra",
|
|
196
|
+
"is",
|
|
197
|
+
"for",
|
|
198
|
+
"me",
|
|
199
|
+
"my",
|
|
200
|
+
"and",
|
|
201
|
+
].includes(w.toLowerCase())
|
|
202
|
+
)
|
|
203
|
+
.slice(0, 2)
|
|
204
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
205
|
+
.join("") || "EbadeApp",
|
|
206
|
+
type: type,
|
|
207
|
+
description: prompt,
|
|
208
|
+
features: features.length > 0 ? features : ["Turnkey Scaffold"],
|
|
209
|
+
design: {
|
|
210
|
+
colors: { primary },
|
|
211
|
+
style: "glass-modern",
|
|
212
|
+
},
|
|
213
|
+
pages: pages,
|
|
214
|
+
api: [{ path: "/api/data", methods: ["GET"], intent: "fetch-data" }],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return config;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
61
221
|
// ============================================
|
|
62
222
|
// ebade Parser
|
|
63
223
|
// ============================================
|
|
@@ -96,17 +256,19 @@ import { cn } from "@/lib/utils";
|
|
|
96
256
|
/**
|
|
97
257
|
* ๐ง Generated via ebade
|
|
98
258
|
* Component: ${toPascalCase(componentName)}
|
|
99
|
-
* Status:
|
|
259
|
+
* Status: Intent needs implementation
|
|
100
260
|
*/
|
|
101
261
|
export function ${toPascalCase(componentName)}() {
|
|
102
262
|
return (
|
|
103
|
-
<div className="p-12
|
|
104
|
-
<div className="w-
|
|
105
|
-
<span className="text-
|
|
263
|
+
<div className="p-12 glass-card rounded-[2.5rem] text-center min-h-[300px] flex flex-col items-center justify-center group hover:border-primary/50 transition-all">
|
|
264
|
+
<div className="w-20 h-20 bg-primary/10 rounded-[2rem] flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
|
265
|
+
<span className="text-3xl">โจ</span>
|
|
106
266
|
</div>
|
|
107
|
-
<h3 className="text-
|
|
108
|
-
|
|
109
|
-
|
|
267
|
+
<h3 className="text-2xl font-bold mb-3 text-white">${toPascalCase(
|
|
268
|
+
componentName
|
|
269
|
+
)}</h3>
|
|
270
|
+
<p className="text-slate-400 max-w-sm mx-auto leading-relaxed">
|
|
271
|
+
This intent is defined for your AI agent. To customize, edit <code>components/${componentName}.tsx</code> or use the ebade compiler.
|
|
110
272
|
</p>
|
|
111
273
|
</div>
|
|
112
274
|
);
|
|
@@ -176,19 +338,15 @@ ${componentImports}
|
|
|
176
338
|
*/
|
|
177
339
|
export default function ${toPascalCase(page.intent)}Page() {
|
|
178
340
|
return (
|
|
179
|
-
<div className="min-h-screen bg-slate-950 text-white">
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
page.intent
|
|
184
|
-
)}</h1>
|
|
185
|
-
<p className="text-sm opacity-40 mt-1">Route: ${page.path}</p>
|
|
186
|
-
</header>
|
|
341
|
+
<div className="min-h-screen bg-slate-950 text-white selection:bg-indigo-500/30 selection:text-indigo-200">
|
|
342
|
+
<main className="relative overflow-hidden">
|
|
343
|
+
{/* Ambient background glow */}
|
|
344
|
+
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-[500px] bg-indigo-600/10 blur-[120px] rounded-full pointer-events-none" />
|
|
187
345
|
|
|
188
|
-
<
|
|
346
|
+
<div className="relative z-10">
|
|
189
347
|
${componentUsage}
|
|
190
|
-
</
|
|
191
|
-
</
|
|
348
|
+
</div>
|
|
349
|
+
</main>
|
|
192
350
|
</div>
|
|
193
351
|
);
|
|
194
352
|
}
|
|
@@ -384,6 +542,16 @@ export default nextConfig;
|
|
|
384
542
|
`;
|
|
385
543
|
}
|
|
386
544
|
|
|
545
|
+
function generatePostcssConfig() {
|
|
546
|
+
return `module.exports = {
|
|
547
|
+
plugins: {
|
|
548
|
+
tailwindcss: {},
|
|
549
|
+
autoprefixer: {},
|
|
550
|
+
},
|
|
551
|
+
}
|
|
552
|
+
`;
|
|
553
|
+
}
|
|
554
|
+
|
|
387
555
|
function generateTsConfig() {
|
|
388
556
|
return JSON.stringify(
|
|
389
557
|
{
|
|
@@ -459,65 +627,35 @@ function generateGlobalsCss(design) {
|
|
|
459
627
|
|
|
460
628
|
@layer base {
|
|
461
629
|
:root {
|
|
462
|
-
--background:
|
|
463
|
-
--foreground:
|
|
464
|
-
|
|
465
|
-
--card: 0 0% 100%;
|
|
466
|
-
--card-foreground: 222.2 84% 4.9%;
|
|
467
|
-
|
|
468
|
-
--popover: 0 0% 100%;
|
|
469
|
-
--popover-foreground: 222.2 84% 4.9%;
|
|
470
|
-
|
|
471
|
-
--primary: 221.2 83.2% 53.3%;
|
|
472
|
-
--primary-foreground: 210 40% 98%;
|
|
473
|
-
|
|
474
|
-
--secondary: 210 40% 96.1%;
|
|
475
|
-
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
476
|
-
|
|
477
|
-
--muted: 210 40% 96.1%;
|
|
478
|
-
--muted-foreground: 215.4 16.3% 46.9%;
|
|
479
|
-
|
|
480
|
-
--accent: 210 40% 96.1%;
|
|
481
|
-
--accent-foreground: 222.2 47.4% 11.2%;
|
|
482
|
-
|
|
483
|
-
--destructive: 0 84.2% 60.2%;
|
|
484
|
-
--destructive-foreground: 210 40% 98%;
|
|
485
|
-
|
|
486
|
-
--border: 214.3 31.8% 91.4%;
|
|
487
|
-
--input: 214.3 31.8% 91.4%;
|
|
488
|
-
--ring: 221.2 83.2% 53.3%;
|
|
489
|
-
|
|
490
|
-
--radius: 0.5rem;
|
|
491
|
-
}
|
|
630
|
+
--background: 222 47% 4%;
|
|
631
|
+
--foreground: 213 31% 91%;
|
|
492
632
|
|
|
493
|
-
|
|
494
|
-
--
|
|
495
|
-
--foreground: 210 40% 98%;
|
|
633
|
+
--card: 222 47% 4%;
|
|
634
|
+
--card-foreground: 213 31% 91%;
|
|
496
635
|
|
|
497
|
-
--
|
|
498
|
-
--
|
|
636
|
+
--popover: 222 47% 4%;
|
|
637
|
+
--popover-foreground: 213 31% 91%;
|
|
499
638
|
|
|
500
|
-
--
|
|
501
|
-
--
|
|
639
|
+
--primary: ${hexToHsl(primary)};
|
|
640
|
+
--primary-foreground: 0 0% 100%;
|
|
502
641
|
|
|
503
|
-
--
|
|
504
|
-
--
|
|
642
|
+
--secondary: 222 47% 11%;
|
|
643
|
+
--secondary-foreground: 213 31% 91%;
|
|
505
644
|
|
|
506
|
-
--
|
|
507
|
-
--
|
|
645
|
+
--muted: 223 47% 11%;
|
|
646
|
+
--muted-foreground: 215.4 16.3% 56.9%;
|
|
508
647
|
|
|
509
|
-
--
|
|
510
|
-
--muted-foreground: 215 20.2% 65.1%;
|
|
511
|
-
|
|
512
|
-
--accent: 217.2 32.6% 17.5%;
|
|
648
|
+
--accent: 216 34% 17%;
|
|
513
649
|
--accent-foreground: 210 40% 98%;
|
|
514
650
|
|
|
515
|
-
--destructive: 0
|
|
651
|
+
--destructive: 0 63% 31%;
|
|
516
652
|
--destructive-foreground: 210 40% 98%;
|
|
517
653
|
|
|
518
|
-
--border:
|
|
519
|
-
--input:
|
|
520
|
-
--ring:
|
|
654
|
+
--border: 216 34% 17%;
|
|
655
|
+
--input: 216 34% 17%;
|
|
656
|
+
--ring: ${hexToHsl(primary)};
|
|
657
|
+
|
|
658
|
+
--radius: 1rem;
|
|
521
659
|
}
|
|
522
660
|
}
|
|
523
661
|
|
|
@@ -526,9 +664,25 @@ function generateGlobalsCss(design) {
|
|
|
526
664
|
@apply border-border;
|
|
527
665
|
}
|
|
528
666
|
body {
|
|
529
|
-
@apply bg-background text-foreground;
|
|
667
|
+
@apply bg-background text-foreground antialiased;
|
|
668
|
+
font-feature-settings: "cv11", "ss01";
|
|
530
669
|
}
|
|
531
670
|
}
|
|
671
|
+
|
|
672
|
+
/* Premium Animations */
|
|
673
|
+
@keyframes float {
|
|
674
|
+
0% { transform: translateY(0px); }
|
|
675
|
+
50% { transform: translateY(-10px); }
|
|
676
|
+
100% { transform: translateY(0px); }
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.animate-float {
|
|
680
|
+
animation: float 6s ease-in-out infinite;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.glass-card {
|
|
684
|
+
@apply bg-white/[0.03] border border-white/10 backdrop-blur-xl;
|
|
685
|
+
}
|
|
532
686
|
`;
|
|
533
687
|
}
|
|
534
688
|
|
|
@@ -542,6 +696,61 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
542
696
|
`;
|
|
543
697
|
}
|
|
544
698
|
|
|
699
|
+
function generateGitignore() {
|
|
700
|
+
return `# dependencies
|
|
701
|
+
/node_modules
|
|
702
|
+
/.pnp
|
|
703
|
+
.pnp.js
|
|
704
|
+
|
|
705
|
+
# testing
|
|
706
|
+
/coverage
|
|
707
|
+
|
|
708
|
+
# next.js
|
|
709
|
+
/.next/
|
|
710
|
+
/out/
|
|
711
|
+
|
|
712
|
+
# production
|
|
713
|
+
/build
|
|
714
|
+
|
|
715
|
+
# misc
|
|
716
|
+
.DS_Store
|
|
717
|
+
*.pem
|
|
718
|
+
|
|
719
|
+
# debug
|
|
720
|
+
npm-debug.log*
|
|
721
|
+
yarn-debug.log*
|
|
722
|
+
yarn-error.log*
|
|
723
|
+
|
|
724
|
+
# local env files
|
|
725
|
+
.env*.local
|
|
726
|
+
.env
|
|
727
|
+
|
|
728
|
+
# vercel
|
|
729
|
+
.vercel
|
|
730
|
+
|
|
731
|
+
# typescript
|
|
732
|
+
*.tsbuildinfo
|
|
733
|
+
next-env.d.ts
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function generateEnvExample(config) {
|
|
738
|
+
return `# ebade Generated Environment Variables
|
|
739
|
+
# Project: ${config.name}
|
|
740
|
+
|
|
741
|
+
# Database (Supabase / Postgres)
|
|
742
|
+
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
|
|
743
|
+
|
|
744
|
+
# Authentication (NextAuth / Clerk)
|
|
745
|
+
NEXTAUTH_SECRET="your-secret-here"
|
|
746
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=""
|
|
747
|
+
|
|
748
|
+
# API Keys
|
|
749
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""
|
|
750
|
+
STRIPE_SECRET_KEY=""
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
|
|
545
754
|
// ============================================
|
|
546
755
|
// Design System CSS Generator
|
|
547
756
|
// ============================================
|
|
@@ -627,20 +836,64 @@ Built with ebade - The Agent-First Framework for the next era of development.
|
|
|
627
836
|
// ============================================
|
|
628
837
|
// Utility Functions
|
|
629
838
|
// ============================================
|
|
630
|
-
function toPascalCase(str) {
|
|
839
|
+
export function toPascalCase(str) {
|
|
631
840
|
return str
|
|
632
|
-
.split(
|
|
841
|
+
.split(/[-_]/)
|
|
633
842
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
634
843
|
.join("");
|
|
635
844
|
}
|
|
636
845
|
|
|
637
|
-
function toSnakeCase(str) {
|
|
846
|
+
export function toSnakeCase(str) {
|
|
638
847
|
return str
|
|
639
|
-
.replace(/
|
|
848
|
+
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
|
849
|
+
.replace(/-/g, "_")
|
|
640
850
|
.toLowerCase()
|
|
641
851
|
.replace(/^_/, "");
|
|
642
852
|
}
|
|
643
853
|
|
|
854
|
+
export function hexToHsl(hex) {
|
|
855
|
+
let r = 0,
|
|
856
|
+
g = 0,
|
|
857
|
+
b = 0;
|
|
858
|
+
if (hex.length === 4) {
|
|
859
|
+
r = parseInt(hex[1] + hex[1], 16);
|
|
860
|
+
g = parseInt(hex[2] + hex[2], 16);
|
|
861
|
+
b = parseInt(hex[3] + hex[3], 16);
|
|
862
|
+
} else if (hex.length === 7) {
|
|
863
|
+
r = parseInt(hex.slice(1, 3), 16);
|
|
864
|
+
g = parseInt(hex.slice(3, 5), 16);
|
|
865
|
+
b = parseInt(hex.slice(5, 7), 16);
|
|
866
|
+
}
|
|
867
|
+
r /= 255;
|
|
868
|
+
g /= 255;
|
|
869
|
+
b /= 255;
|
|
870
|
+
let max = Math.max(r, g, b),
|
|
871
|
+
min = Math.min(r, g, b);
|
|
872
|
+
let h,
|
|
873
|
+
s,
|
|
874
|
+
l = (max + min) / 2;
|
|
875
|
+
if (max === min) h = s = 0;
|
|
876
|
+
else {
|
|
877
|
+
let d = max - min;
|
|
878
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
879
|
+
switch (max) {
|
|
880
|
+
case r:
|
|
881
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
882
|
+
break;
|
|
883
|
+
case g:
|
|
884
|
+
h = (b - r) / d + 2;
|
|
885
|
+
break;
|
|
886
|
+
case b:
|
|
887
|
+
h = (r - g) / d + 4;
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
h /= 6;
|
|
891
|
+
}
|
|
892
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(
|
|
893
|
+
l * 100
|
|
894
|
+
)}%`;
|
|
895
|
+
}
|
|
896
|
+
|
|
644
897
|
function mapToSqlType(type) {
|
|
645
898
|
const typeMap = {
|
|
646
899
|
uuid: "UUID PRIMARY KEY DEFAULT gen_random_uuid()",
|
|
@@ -686,7 +939,10 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
686
939
|
);
|
|
687
940
|
|
|
688
941
|
// Create output directory structure
|
|
689
|
-
|
|
942
|
+
// If outputDir already ends with config.name, don't nest again
|
|
943
|
+
const projectDir = outputDir.endsWith(config.name)
|
|
944
|
+
? outputDir
|
|
945
|
+
: path.join(outputDir, config.name);
|
|
690
946
|
ensureDir(projectDir);
|
|
691
947
|
|
|
692
948
|
// ========== Generate Structure ==========
|
|
@@ -751,8 +1007,11 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
751
1007
|
|
|
752
1008
|
fs.writeFileSync(path.join(projectDir, componentPath), content.trim());
|
|
753
1009
|
|
|
754
|
-
// Generate unit test
|
|
755
|
-
const testPath = `components/${component}.test.tsx`;
|
|
1010
|
+
// Generate unit test in tests/ directory
|
|
1011
|
+
const testPath = `tests/components/${component}.test.tsx`;
|
|
1012
|
+
const testDir = path.dirname(path.join(projectDir, testPath));
|
|
1013
|
+
ensureDir(testDir);
|
|
1014
|
+
|
|
756
1015
|
fs.writeFileSync(
|
|
757
1016
|
path.join(projectDir, testPath),
|
|
758
1017
|
generateComponentTest(component).trim()
|
|
@@ -771,7 +1030,12 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
771
1030
|
if (config.api) {
|
|
772
1031
|
const spinner3 = ora("Generating API routes...").start();
|
|
773
1032
|
config.api.forEach((endpoint) => {
|
|
774
|
-
|
|
1033
|
+
// Fix potential /api/api double nesting
|
|
1034
|
+
const cleanPath = endpoint.path.startsWith("/api")
|
|
1035
|
+
? endpoint.path.slice(4)
|
|
1036
|
+
: endpoint.path;
|
|
1037
|
+
|
|
1038
|
+
const apiPath = `app/api${cleanPath}/route.ts`;
|
|
775
1039
|
const apiDir = path.dirname(path.join(projectDir, apiPath));
|
|
776
1040
|
ensureDir(apiDir);
|
|
777
1041
|
|
|
@@ -820,6 +1084,13 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
820
1084
|
);
|
|
821
1085
|
log.file("tailwind.config.js");
|
|
822
1086
|
|
|
1087
|
+
// postcss.config.js
|
|
1088
|
+
fs.writeFileSync(
|
|
1089
|
+
path.join(projectDir, "postcss.config.js"),
|
|
1090
|
+
generatePostcssConfig()
|
|
1091
|
+
);
|
|
1092
|
+
log.file("postcss.config.js");
|
|
1093
|
+
|
|
823
1094
|
// vitest.config.ts
|
|
824
1095
|
fs.writeFileSync(
|
|
825
1096
|
path.join(projectDir, "vitest.config.ts"),
|
|
@@ -827,6 +1098,17 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
827
1098
|
);
|
|
828
1099
|
log.file("vitest.config.ts");
|
|
829
1100
|
|
|
1101
|
+
// .gitignore
|
|
1102
|
+
fs.writeFileSync(path.join(projectDir, ".gitignore"), generateGitignore());
|
|
1103
|
+
log.file(".gitignore");
|
|
1104
|
+
|
|
1105
|
+
// .env.example
|
|
1106
|
+
fs.writeFileSync(
|
|
1107
|
+
path.join(projectDir, ".env.example"),
|
|
1108
|
+
generateEnvExample(config)
|
|
1109
|
+
);
|
|
1110
|
+
log.file(".env.example");
|
|
1111
|
+
|
|
830
1112
|
// app/layout.tsx
|
|
831
1113
|
fs.writeFileSync(
|
|
832
1114
|
path.join(projectDir, "app/layout.tsx"),
|
|
@@ -990,13 +1272,14 @@ async function verifyOutput(projectDir, config) {
|
|
|
990
1272
|
}
|
|
991
1273
|
|
|
992
1274
|
// 3. Test Coverage Check
|
|
993
|
-
// Ensure every component has a matching test file
|
|
1275
|
+
// Ensure every component has a matching test file in tests/components/
|
|
994
1276
|
const components = fs
|
|
995
1277
|
.readdirSync(path.join(projectDir, "components"))
|
|
996
1278
|
.filter((f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx"));
|
|
997
1279
|
components.forEach((comp) => {
|
|
998
1280
|
const testFile = comp.replace(".tsx", ".test.tsx");
|
|
999
|
-
|
|
1281
|
+
const testPath = path.join(projectDir, "tests/components", testFile);
|
|
1282
|
+
if (!fs.existsSync(testPath)) {
|
|
1000
1283
|
results.tests = false;
|
|
1001
1284
|
results.issues.push(`Missing test for component: ${comp}`);
|
|
1002
1285
|
}
|
|
@@ -1058,12 +1341,14 @@ ${colors.dim}Usage:${colors.reset}
|
|
|
1058
1341
|
|
|
1059
1342
|
${colors.dim}Commands:${colors.reset}
|
|
1060
1343
|
init Create a new ebade project interactively
|
|
1344
|
+
build <prompt> Generate and scaffold a project from a natural language prompt
|
|
1061
1345
|
scaffold <file> [output] Scaffold a project from ebade file
|
|
1062
1346
|
dev <file> [output] Watch ebade file and re-scaffold on changes
|
|
1063
1347
|
playground Open the ebade playground
|
|
1064
1348
|
|
|
1065
1349
|
${colors.dim}Examples:${colors.reset}
|
|
1066
1350
|
npx ebade init
|
|
1351
|
+
npx ebade build "A violet themed crypto dashboard"
|
|
1067
1352
|
npx ebade scaffold examples/saas-dashboard.ebade.yaml ./output
|
|
1068
1353
|
npx ebade dev my-project.ebade.yaml ./my-app
|
|
1069
1354
|
|
|
@@ -1227,80 +1512,134 @@ ${LOGO}
|
|
|
1227
1512
|
// ============================================
|
|
1228
1513
|
// Command Router
|
|
1229
1514
|
// ============================================
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1515
|
+
const isMain =
|
|
1516
|
+
process.argv[1] &&
|
|
1517
|
+
(process.argv[1].endsWith("scaffold.js") ||
|
|
1518
|
+
process.argv[1].endsWith("ebade"));
|
|
1519
|
+
|
|
1520
|
+
if (isMain) {
|
|
1521
|
+
if (
|
|
1522
|
+
args.length === 0 ||
|
|
1523
|
+
command === "help" ||
|
|
1524
|
+
command === "--help" ||
|
|
1525
|
+
command === "-h"
|
|
1526
|
+
) {
|
|
1527
|
+
showHelp();
|
|
1528
|
+
process.exit(0);
|
|
1529
|
+
}
|
|
1239
1530
|
|
|
1240
|
-
if (command === "init") {
|
|
1241
|
-
|
|
1242
|
-
} else if (command === "scaffold") {
|
|
1243
|
-
|
|
1244
|
-
|
|
1531
|
+
if (command === "init") {
|
|
1532
|
+
await init();
|
|
1533
|
+
} else if (command === "scaffold") {
|
|
1534
|
+
const ebadeFile = args[1];
|
|
1535
|
+
const outputDir = args[2] || "./output";
|
|
1245
1536
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1537
|
+
if (!ebadeFile) {
|
|
1538
|
+
console.error(
|
|
1539
|
+
`${colors.red}Error:${colors.reset} Please provide an ebade file path.`
|
|
1540
|
+
);
|
|
1541
|
+
console.log(
|
|
1542
|
+
`\n${colors.dim}Usage:${colors.reset} npx ebade scaffold <file.ebade.yaml> [output-dir]\n`
|
|
1543
|
+
);
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1255
1546
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1547
|
+
if (!fs.existsSync(ebadeFile)) {
|
|
1548
|
+
console.error(
|
|
1549
|
+
`${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
|
|
1550
|
+
);
|
|
1551
|
+
process.exit(1);
|
|
1552
|
+
}
|
|
1262
1553
|
|
|
1263
|
-
|
|
1264
|
-
} else if (command === "
|
|
1265
|
-
|
|
1266
|
-
const url = "https://ebade.dev/playground";
|
|
1267
|
-
const start =
|
|
1268
|
-
process.platform === "darwin"
|
|
1269
|
-
? "open"
|
|
1270
|
-
: process.platform === "win32"
|
|
1271
|
-
? "start"
|
|
1272
|
-
: "xdg-open";
|
|
1273
|
-
try {
|
|
1274
|
-
execSync(`${start} ${url}`);
|
|
1275
|
-
} catch (e) {
|
|
1276
|
-
console.log(`\n${colors.yellow}Please open:${colors.reset} ${url}`);
|
|
1277
|
-
}
|
|
1278
|
-
} else if (command === "dev") {
|
|
1279
|
-
const ebadeFile = args[1];
|
|
1280
|
-
const outputDir = args[2] || "./output";
|
|
1554
|
+
await scaffold(ebadeFile, outputDir);
|
|
1555
|
+
} else if (command === "build") {
|
|
1556
|
+
const prompt = args.slice(1).join(" ");
|
|
1281
1557
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1558
|
+
if (!prompt) {
|
|
1559
|
+
console.error(
|
|
1560
|
+
`${colors.red}Error:${colors.reset} Please provide a prompt. e.g. npx ebade build "A blue themed SaaS"`
|
|
1561
|
+
);
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
const spinner = ora(
|
|
1566
|
+
`${colors.cyan}EbadeArchitect is designing your project...${colors.reset}`
|
|
1567
|
+
).start();
|
|
1568
|
+
const config = await EbadeArchitect.plan(prompt);
|
|
1569
|
+
spinner.succeed(
|
|
1570
|
+
`Design complete: ${colors.bright}${config.name}${colors.reset}`
|
|
1285
1571
|
);
|
|
1572
|
+
|
|
1573
|
+
const outputDir = "./" + (config.name || "ebade-app");
|
|
1574
|
+
|
|
1575
|
+
// Create temporary YAML and scaffold
|
|
1576
|
+
const tempDir = path.join(process.cwd(), ".ebade_temp");
|
|
1577
|
+
ensureDir(tempDir);
|
|
1578
|
+
const tempFile = path.join(tempDir, "project.ebade.yaml");
|
|
1579
|
+
fs.writeFileSync(tempFile, yaml.stringify(config));
|
|
1580
|
+
|
|
1581
|
+
await scaffold(tempFile, outputDir);
|
|
1582
|
+
|
|
1583
|
+
// Final Brief
|
|
1584
|
+
console.log(`
|
|
1585
|
+
${colors.magenta}${colors.bright}๐จ Creative Brief from EbadeArchitect:${
|
|
1586
|
+
colors.reset
|
|
1587
|
+
}
|
|
1588
|
+
${colors.cyan}Project:${colors.reset} ${config.name}
|
|
1589
|
+
${colors.cyan}Theme:${colors.reset} ${
|
|
1590
|
+
config.design.colors.primary
|
|
1591
|
+
} (Detected from prompt)
|
|
1592
|
+
${colors.cyan}Pages:${colors.reset} ${config.pages
|
|
1593
|
+
.map((p) => p.path)
|
|
1594
|
+
.join(", ")}
|
|
1595
|
+
${colors.cyan}Features:${colors.reset} ${config.features.join(", ")}
|
|
1596
|
+
`);
|
|
1597
|
+
|
|
1598
|
+
// Cleanup
|
|
1599
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1600
|
+
} else if (command === "playground") {
|
|
1286
1601
|
console.log(
|
|
1287
|
-
`\n${colors.
|
|
1602
|
+
`\n${colors.cyan}๐ Opening ebade playground...${colors.reset}`
|
|
1288
1603
|
);
|
|
1289
|
-
|
|
1290
|
-
|
|
1604
|
+
const url = "https://ebade.dev/playground";
|
|
1605
|
+
const start =
|
|
1606
|
+
process.platform === "darwin"
|
|
1607
|
+
? "open"
|
|
1608
|
+
: process.platform === "win32"
|
|
1609
|
+
? "start"
|
|
1610
|
+
: "xdg-open";
|
|
1611
|
+
try {
|
|
1612
|
+
execSync(`${start} ${url}`);
|
|
1613
|
+
} catch (e) {
|
|
1614
|
+
console.log(`\n${colors.yellow}Please open:${colors.reset} ${url}`);
|
|
1615
|
+
}
|
|
1616
|
+
} else if (command === "dev") {
|
|
1617
|
+
const ebadeFile = args[1];
|
|
1618
|
+
const outputDir = args[2] || "./output";
|
|
1619
|
+
|
|
1620
|
+
if (!ebadeFile) {
|
|
1621
|
+
console.error(
|
|
1622
|
+
`${colors.red}Error:${colors.reset} Please provide an ebade file path.`
|
|
1623
|
+
);
|
|
1624
|
+
console.log(
|
|
1625
|
+
`\n${colors.dim}Usage:${colors.reset} npx ebade dev <file.ebade.yaml> [output-dir]\n`
|
|
1626
|
+
);
|
|
1627
|
+
process.exit(1);
|
|
1628
|
+
}
|
|
1291
1629
|
|
|
1292
|
-
|
|
1630
|
+
if (!fs.existsSync(ebadeFile)) {
|
|
1631
|
+
console.error(
|
|
1632
|
+
`${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
|
|
1633
|
+
);
|
|
1634
|
+
process.exit(1);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
await dev(ebadeFile, outputDir);
|
|
1638
|
+
} else {
|
|
1293
1639
|
console.error(
|
|
1294
|
-
`${colors.red}Error:${colors.reset}
|
|
1640
|
+
`${colors.red}Error:${colors.reset} Unknown command: ${command}`
|
|
1295
1641
|
);
|
|
1642
|
+
showHelp();
|
|
1296
1643
|
process.exit(1);
|
|
1297
1644
|
}
|
|
1298
|
-
|
|
1299
|
-
await dev(ebadeFile, outputDir);
|
|
1300
|
-
} else {
|
|
1301
|
-
console.error(
|
|
1302
|
-
`${colors.red}Error:${colors.reset} Unknown command: ${command}`
|
|
1303
|
-
);
|
|
1304
|
-
showHelp();
|
|
1305
|
-
process.exit(1);
|
|
1306
1645
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebade-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "MCP Server for ebade v0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "MCP Server for ebade v0.4.6 - The Agent-First Framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Validate ebade files
|
|
10
10
|
* - Compile ebade to framework-specific code
|
|
11
11
|
* - Generate components from natural language descriptions
|
|
12
|
+
* - Build entire projects from natural language prompts (NEW in v0.4.6)
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -24,12 +25,13 @@ import { scaffoldProject } from "./tools/scaffold.js";
|
|
|
24
25
|
import { validateIntent } from "./tools/validate.js";
|
|
25
26
|
import { compileIntent } from "./tools/compile.js";
|
|
26
27
|
import { generateComponent } from "./tools/generate.js";
|
|
28
|
+
import { buildFromPrompt } from "./tools/build.js";
|
|
27
29
|
|
|
28
30
|
// Create the MCP server
|
|
29
31
|
const server = new Server(
|
|
30
32
|
{
|
|
31
33
|
name: "ebade",
|
|
32
|
-
version: "0.
|
|
34
|
+
version: "0.4.6",
|
|
33
35
|
},
|
|
34
36
|
{
|
|
35
37
|
capabilities: {
|
|
@@ -183,6 +185,33 @@ Use this when:
|
|
|
183
185
|
required: ["description"],
|
|
184
186
|
},
|
|
185
187
|
},
|
|
188
|
+
{
|
|
189
|
+
name: "ebade_build",
|
|
190
|
+
description: `Revolutionary "Prompt-to-Product" tool.
|
|
191
|
+
Generates a complete, production-ready project from a single natural language description.
|
|
192
|
+
|
|
193
|
+
Use this when the user says:
|
|
194
|
+
- "Bana mor temalฤฑ bir kripto borsasฤฑ yap"
|
|
195
|
+
- "Create a red themed SaaS for AI model store"
|
|
196
|
+
- "Make a sleek portfolio for a creative director"
|
|
197
|
+
|
|
198
|
+
This tool handles architecture, component selection, color palette, and scaffolding in one shot.`,
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
prompt: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "The natural language instruction for the project",
|
|
205
|
+
},
|
|
206
|
+
outputDir: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description:
|
|
209
|
+
"Base directory path where the project will be created (absolute)",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
required: ["prompt", "outputDir"],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
186
215
|
],
|
|
187
216
|
};
|
|
188
217
|
});
|
|
@@ -229,6 +258,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
229
258
|
style: args?.style as any,
|
|
230
259
|
});
|
|
231
260
|
|
|
261
|
+
case "ebade_build":
|
|
262
|
+
return {
|
|
263
|
+
content: [
|
|
264
|
+
{
|
|
265
|
+
type: "text",
|
|
266
|
+
text: await buildFromPrompt(args as any),
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
};
|
|
270
|
+
|
|
232
271
|
default:
|
|
233
272
|
throw new Error(`Unknown tool: ${name}`);
|
|
234
273
|
}
|
|
@@ -278,7 +317,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
278
317
|
const { uri } = request.params;
|
|
279
318
|
|
|
280
319
|
const resources: Record<string, string> = {
|
|
281
|
-
"ebade://syntax": `# ebade Syntax Reference
|
|
320
|
+
"ebade://syntax": `# ebade Syntax Reference\n\n@page, @ebade, @requires, @outcomes, @data, @validate, @style, @compose, @on, @expects\n\nCode = f(ebade)`,
|
|
282
321
|
"ebade://examples/ecommerce": `# E-commerce ebade\nname: my-store\ntype: e-commerce\nfeatures:\n - product-catalog\n - shopping-cart\n - checkout`,
|
|
283
322
|
"ebade://examples/saas": `# SaaS ebade\nname: my-saas\ntype: saas-dashboard\nfeatures:\n - user-auth\n - billing\n - analytics`,
|
|
284
323
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
interface BuildArgs {
|
|
6
|
+
prompt: string;
|
|
7
|
+
outputDir: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function buildFromPrompt(args: BuildArgs): Promise<string> {
|
|
11
|
+
const { prompt, outputDir } = args;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Find the CLI script path
|
|
15
|
+
// Assuming we are in packages/mcp-server/dist/tools/ (at runtime)
|
|
16
|
+
// or packages/mcp-server/src/tools/ (at dev time)
|
|
17
|
+
const cliPath = path.resolve(process.cwd(), "../../cli/scaffold.js");
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(cliPath)) {
|
|
20
|
+
throw new Error(`CLI not found at ${cliPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log(`๐ Executing ebade build from prompt: "${prompt}"`);
|
|
24
|
+
|
|
25
|
+
// Execute the CLI command
|
|
26
|
+
// We pass the prompt as an argument.
|
|
27
|
+
// We need to handle the outputDir carefully.
|
|
28
|
+
// The CLI build command currently uses its own naming logic,
|
|
29
|
+
// but it creates the folder in the current process.cwd().
|
|
30
|
+
|
|
31
|
+
const cmd = `node "${cliPath}" build "${prompt}"`;
|
|
32
|
+
|
|
33
|
+
// Run in the specified outputDir if provided
|
|
34
|
+
const result = execSync(cmd, {
|
|
35
|
+
cwd: outputDir,
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return `โ
ebade v0.4.6 Build Complete!
|
|
41
|
+
|
|
42
|
+
Prompt: "${prompt}"
|
|
43
|
+
Output: ${outputDir}
|
|
44
|
+
|
|
45
|
+
CLI Output:
|
|
46
|
+
${result}
|
|
47
|
+
|
|
48
|
+
Next Steps:
|
|
49
|
+
1. Navigate to the generated folder.
|
|
50
|
+
2. Run 'npm install && npm run dev'.
|
|
51
|
+
3. Enjoy your turnkey project!`;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Build failed: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import assert from "assert";
|
|
5
|
+
|
|
6
|
+
async function runCliTests() {
|
|
7
|
+
console.log("๐งช Running CLI Integration Tests...\n");
|
|
8
|
+
|
|
9
|
+
const PROJECT_NAME = "ModernBlog";
|
|
10
|
+
if (fs.existsSync(PROJECT_NAME)) {
|
|
11
|
+
fs.rmSync(PROJECT_NAME, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// 1. Test 'ebade build' command
|
|
16
|
+
console.log(`Testing 'ebade build' command for ${PROJECT_NAME}...`);
|
|
17
|
+
const cmd = `node cli/scaffold.js build "A Modern Blog with blue theme"`;
|
|
18
|
+
|
|
19
|
+
execSync(cmd, { stdio: "inherit" });
|
|
20
|
+
|
|
21
|
+
const projectDir = path.join(process.cwd(), PROJECT_NAME);
|
|
22
|
+
|
|
23
|
+
assert.ok(
|
|
24
|
+
fs.existsSync(projectDir),
|
|
25
|
+
`Project directory (${PROJECT_NAME}) should exist`
|
|
26
|
+
);
|
|
27
|
+
assert.ok(
|
|
28
|
+
fs.existsSync(path.join(projectDir, "project.ebade.yaml")),
|
|
29
|
+
"project.ebade.yaml should be generated"
|
|
30
|
+
);
|
|
31
|
+
assert.ok(
|
|
32
|
+
fs.existsSync(path.join(projectDir, "app/page.tsx")),
|
|
33
|
+
"Main page should be generated"
|
|
34
|
+
);
|
|
35
|
+
assert.ok(
|
|
36
|
+
fs.existsSync(path.join(projectDir, ".env.example")),
|
|
37
|
+
".env.example should be generated"
|
|
38
|
+
);
|
|
39
|
+
assert.ok(
|
|
40
|
+
fs.existsSync(path.join(projectDir, ".gitignore")),
|
|
41
|
+
".gitignore should be generated"
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Verify blog pages
|
|
45
|
+
assert.ok(
|
|
46
|
+
fs.existsSync(path.join(projectDir, "app/posts/page.tsx")),
|
|
47
|
+
"Blog index page should be generated"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
console.log("โ
'ebade build' Integration OK");
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("\nโ CLI Test failed:");
|
|
53
|
+
console.error(err);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
} finally {
|
|
56
|
+
// Cleanup
|
|
57
|
+
console.log("\n๐งน Cleaning up test artifacts...");
|
|
58
|
+
if (fs.existsSync(PROJECT_NAME)) {
|
|
59
|
+
fs.rmSync(PROJECT_NAME, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log("\nโจ CLI integration tests passed!");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
runCliTests();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EbadeArchitect,
|
|
3
|
+
toPascalCase,
|
|
4
|
+
toSnakeCase,
|
|
5
|
+
hexToHsl,
|
|
6
|
+
} from "../../cli/scaffold.js";
|
|
7
|
+
import assert from "assert";
|
|
8
|
+
|
|
9
|
+
async function runTests() {
|
|
10
|
+
console.log("๐งช Running Framework Unit Tests (v0.4.6)...\n");
|
|
11
|
+
|
|
12
|
+
// 1. Utility Functions
|
|
13
|
+
console.log("Testing Utility Functions...");
|
|
14
|
+
assert.strictEqual(toPascalCase("hello-world"), "HelloWorld");
|
|
15
|
+
assert.strictEqual(toPascalCase("hello_world"), "HelloWorld");
|
|
16
|
+
assert.strictEqual(toSnakeCase("HelloWorld"), "hello_world");
|
|
17
|
+
assert.strictEqual(toSnakeCase("hello-world"), "hello_world");
|
|
18
|
+
|
|
19
|
+
// hexToHsl Test (Critical for Design System)
|
|
20
|
+
const hsl = hexToHsl("#8b5cf6"); // Violet
|
|
21
|
+
assert.ok(
|
|
22
|
+
hsl.includes("258"),
|
|
23
|
+
`HSL for #8b5cf6 should include hue 258, got ${hsl}`
|
|
24
|
+
);
|
|
25
|
+
console.log("โ
Utilities OK");
|
|
26
|
+
|
|
27
|
+
// 2. Architect Mapping
|
|
28
|
+
console.log("\nTesting EbadeArchitect (Complex Intent)...");
|
|
29
|
+
const prompt =
|
|
30
|
+
"Can you make a luxury gold themed dashboard with charts and login features?";
|
|
31
|
+
const config = await EbadeArchitect.plan(prompt);
|
|
32
|
+
|
|
33
|
+
// Check intelligence
|
|
34
|
+
assert.strictEqual(
|
|
35
|
+
config.name,
|
|
36
|
+
"LuxuryGold",
|
|
37
|
+
"Should filter filler words like 'Can you make a'"
|
|
38
|
+
);
|
|
39
|
+
assert.strictEqual(
|
|
40
|
+
config.design.colors.primary,
|
|
41
|
+
"#fbbf24",
|
|
42
|
+
"Gold detection failed"
|
|
43
|
+
);
|
|
44
|
+
assert.ok(
|
|
45
|
+
config.features.includes("Advanced Analytics"),
|
|
46
|
+
"Analytics detection failed"
|
|
47
|
+
);
|
|
48
|
+
assert.ok(
|
|
49
|
+
config.features.includes("Authentication"),
|
|
50
|
+
"Auth detection failed"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Check Type-Aware Pages
|
|
54
|
+
assert.strictEqual(
|
|
55
|
+
config.pages.length,
|
|
56
|
+
2,
|
|
57
|
+
"Should have landing and dashboard"
|
|
58
|
+
);
|
|
59
|
+
assert.ok(
|
|
60
|
+
config.pages.some((p) => p.path === "/dashboard"),
|
|
61
|
+
"Dashboard page missing"
|
|
62
|
+
);
|
|
63
|
+
console.log("โ
Complex Intent mapping OK");
|
|
64
|
+
|
|
65
|
+
// 3. Project Type Specifics (E-commerce)
|
|
66
|
+
console.log("\nTesting EbadeArchitect (E-commerce)...");
|
|
67
|
+
const shopConfig = await EbadeArchitect.plan(
|
|
68
|
+
"Sleek shoe store with red theme"
|
|
69
|
+
);
|
|
70
|
+
assert.strictEqual(shopConfig.type, "e-commerce");
|
|
71
|
+
assert.ok(
|
|
72
|
+
shopConfig.pages.some((p) => p.path === "/products"),
|
|
73
|
+
"E-commerce missing /products"
|
|
74
|
+
);
|
|
75
|
+
assert.ok(
|
|
76
|
+
shopConfig.pages.some((p) => p.path === "/cart"),
|
|
77
|
+
"E-commerce missing /cart"
|
|
78
|
+
);
|
|
79
|
+
console.log("โ
E-commerce structure OK");
|
|
80
|
+
|
|
81
|
+
// 4. Project Type Specifics (Blog)
|
|
82
|
+
console.log("\nTesting EbadeArchitect (Blog)...");
|
|
83
|
+
const blogConfig = await EbadeArchitect.plan("Ocean themed news blog");
|
|
84
|
+
assert.strictEqual(blogConfig.type, "blog");
|
|
85
|
+
assert.ok(
|
|
86
|
+
blogConfig.pages.some((p) => p.path === "/posts"),
|
|
87
|
+
"Blog missing /posts index"
|
|
88
|
+
);
|
|
89
|
+
assert.ok(
|
|
90
|
+
blogConfig.pages.some((p) => p.path === "/posts/[slug]"),
|
|
91
|
+
"Blog missing /posts/[slug] dynamic route"
|
|
92
|
+
);
|
|
93
|
+
console.log("โ
Blog structure OK");
|
|
94
|
+
|
|
95
|
+
console.log("\nโจ All framework tests passed!");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
runTests().catch((err) => {
|
|
99
|
+
console.error("\nโ Test failed:");
|
|
100
|
+
console.error(err);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|