ebade 0.4.3 → 0.4.5
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/CHANGELOG.md +19 -0
- package/cli/scaffold.js +132 -76
- package/package.json +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/www/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.5] - 2025-01-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- UI/UX Overhaul: Premium dark-mode aesthetic with ambient glow and glassmorphism by default.
|
|
12
|
+
- Dynamic Color Support: Real-time Hex-to-HSL conversion to apply user's primary color choice to CSS variables.
|
|
13
|
+
- Component "Intents": Placeholders are now high-quality "Glass Cards" that look like part of a finished UI.
|
|
14
|
+
- Test Organization: All unit tests are now generated in a centralized `tests/` directory instead of being cluttered with components.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- API Pathing: Fixed a bug that caused double-nesting (e.g., `/api/api/...`) in generated routes.
|
|
18
|
+
- Removed debug information (headers, route labels) from generated pages for a "turnkey" production feel.
|
|
19
|
+
|
|
20
|
+
## [0.4.4] - 2025-01-10
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Added `postcss.config.js` generation to enable Tailwind styling in scaffolded projects.
|
|
24
|
+
- Fixed double-nesting issues when initializing projects (removed redundant `projectName/projectName` folder creation).
|
|
25
|
+
- Refined Tailwind config to ensure all component paths are correctly watched.
|
|
26
|
+
|
|
8
27
|
## [0.4.3] - 2025-01-10
|
|
9
28
|
|
|
10
29
|
### 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.5${colors.reset}
|
|
48
48
|
`;
|
|
49
49
|
|
|
50
50
|
const log = {
|
|
@@ -96,17 +96,19 @@ import { cn } from "@/lib/utils";
|
|
|
96
96
|
/**
|
|
97
97
|
* 🧠 Generated via ebade
|
|
98
98
|
* Component: ${toPascalCase(componentName)}
|
|
99
|
-
* Status:
|
|
99
|
+
* Status: Intent needs implementation
|
|
100
100
|
*/
|
|
101
101
|
export function ${toPascalCase(componentName)}() {
|
|
102
102
|
return (
|
|
103
|
-
<div className="p-12
|
|
104
|
-
<div className="w-
|
|
105
|
-
<span className="text-
|
|
103
|
+
<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">
|
|
104
|
+
<div className="w-20 h-20 bg-primary/10 rounded-[2rem] flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
|
|
105
|
+
<span className="text-3xl">✨</span>
|
|
106
106
|
</div>
|
|
107
|
-
<h3 className="text-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
<h3 className="text-2xl font-bold mb-3 text-white">${toPascalCase(
|
|
108
|
+
componentName
|
|
109
|
+
)}</h3>
|
|
110
|
+
<p className="text-slate-400 max-w-sm mx-auto leading-relaxed">
|
|
111
|
+
This intent is defined for your AI agent. To customize, edit <code>components/${componentName}.tsx</code> or use the ebade compiler.
|
|
110
112
|
</p>
|
|
111
113
|
</div>
|
|
112
114
|
);
|
|
@@ -176,19 +178,15 @@ ${componentImports}
|
|
|
176
178
|
*/
|
|
177
179
|
export default function ${toPascalCase(page.intent)}Page() {
|
|
178
180
|
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>
|
|
181
|
+
<div className="min-h-screen bg-slate-950 text-white selection:bg-indigo-500/30 selection:text-indigo-200">
|
|
182
|
+
<main className="relative overflow-hidden">
|
|
183
|
+
{/* Ambient background glow */}
|
|
184
|
+
<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
185
|
|
|
188
|
-
<
|
|
186
|
+
<div className="relative z-10">
|
|
189
187
|
${componentUsage}
|
|
190
|
-
</
|
|
191
|
-
</
|
|
188
|
+
</div>
|
|
189
|
+
</main>
|
|
192
190
|
</div>
|
|
193
191
|
);
|
|
194
192
|
}
|
|
@@ -384,6 +382,16 @@ export default nextConfig;
|
|
|
384
382
|
`;
|
|
385
383
|
}
|
|
386
384
|
|
|
385
|
+
function generatePostcssConfig() {
|
|
386
|
+
return `module.exports = {
|
|
387
|
+
plugins: {
|
|
388
|
+
tailwindcss: {},
|
|
389
|
+
autoprefixer: {},
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
|
|
387
395
|
function generateTsConfig() {
|
|
388
396
|
return JSON.stringify(
|
|
389
397
|
{
|
|
@@ -459,65 +467,35 @@ function generateGlobalsCss(design) {
|
|
|
459
467
|
|
|
460
468
|
@layer base {
|
|
461
469
|
: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
|
-
}
|
|
492
|
-
|
|
493
|
-
.dark {
|
|
494
|
-
--background: 222.2 84% 4.9%;
|
|
495
|
-
--foreground: 210 40% 98%;
|
|
470
|
+
--background: 222 47% 4%;
|
|
471
|
+
--foreground: 213 31% 91%;
|
|
496
472
|
|
|
497
|
-
--card: 222
|
|
498
|
-
--card-foreground:
|
|
473
|
+
--card: 222 47% 4%;
|
|
474
|
+
--card-foreground: 213 31% 91%;
|
|
499
475
|
|
|
500
|
-
--popover: 222
|
|
501
|
-
--popover-foreground:
|
|
476
|
+
--popover: 222 47% 4%;
|
|
477
|
+
--popover-foreground: 213 31% 91%;
|
|
502
478
|
|
|
503
|
-
--primary:
|
|
504
|
-
--primary-foreground:
|
|
479
|
+
--primary: ${hexToHsl(primary)};
|
|
480
|
+
--primary-foreground: 0 0% 100%;
|
|
505
481
|
|
|
506
|
-
--secondary:
|
|
507
|
-
--secondary-foreground:
|
|
482
|
+
--secondary: 222 47% 11%;
|
|
483
|
+
--secondary-foreground: 213 31% 91%;
|
|
508
484
|
|
|
509
|
-
--muted:
|
|
510
|
-
--muted-foreground: 215
|
|
485
|
+
--muted: 223 47% 11%;
|
|
486
|
+
--muted-foreground: 215.4 16.3% 56.9%;
|
|
511
487
|
|
|
512
|
-
--accent:
|
|
488
|
+
--accent: 216 34% 17%;
|
|
513
489
|
--accent-foreground: 210 40% 98%;
|
|
514
490
|
|
|
515
|
-
--destructive: 0
|
|
491
|
+
--destructive: 0 63% 31%;
|
|
516
492
|
--destructive-foreground: 210 40% 98%;
|
|
517
493
|
|
|
518
|
-
--border:
|
|
519
|
-
--input:
|
|
520
|
-
--ring:
|
|
494
|
+
--border: 216 34% 17%;
|
|
495
|
+
--input: 216 34% 17%;
|
|
496
|
+
--ring: ${hexToHsl(primary)};
|
|
497
|
+
|
|
498
|
+
--radius: 1rem;
|
|
521
499
|
}
|
|
522
500
|
}
|
|
523
501
|
|
|
@@ -526,9 +504,25 @@ function generateGlobalsCss(design) {
|
|
|
526
504
|
@apply border-border;
|
|
527
505
|
}
|
|
528
506
|
body {
|
|
529
|
-
@apply bg-background text-foreground;
|
|
507
|
+
@apply bg-background text-foreground antialiased;
|
|
508
|
+
font-feature-settings: "cv11", "ss01";
|
|
530
509
|
}
|
|
531
510
|
}
|
|
511
|
+
|
|
512
|
+
/* Premium Animations */
|
|
513
|
+
@keyframes float {
|
|
514
|
+
0% { transform: translateY(0px); }
|
|
515
|
+
50% { transform: translateY(-10px); }
|
|
516
|
+
100% { transform: translateY(0px); }
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.animate-float {
|
|
520
|
+
animation: float 6s ease-in-out infinite;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.glass-card {
|
|
524
|
+
@apply bg-white/[0.03] border border-white/10 backdrop-blur-xl;
|
|
525
|
+
}
|
|
532
526
|
`;
|
|
533
527
|
}
|
|
534
528
|
|
|
@@ -636,11 +630,54 @@ function toPascalCase(str) {
|
|
|
636
630
|
|
|
637
631
|
function toSnakeCase(str) {
|
|
638
632
|
return str
|
|
639
|
-
.replace(/
|
|
633
|
+
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
|
640
634
|
.toLowerCase()
|
|
641
635
|
.replace(/^_/, "");
|
|
642
636
|
}
|
|
643
637
|
|
|
638
|
+
function hexToHsl(hex) {
|
|
639
|
+
let r = 0,
|
|
640
|
+
g = 0,
|
|
641
|
+
b = 0;
|
|
642
|
+
if (hex.length === 4) {
|
|
643
|
+
r = parseInt(hex[1] + hex[1], 16);
|
|
644
|
+
g = parseInt(hex[2] + hex[2], 16);
|
|
645
|
+
b = parseInt(hex[3] + hex[3], 16);
|
|
646
|
+
} else if (hex.length === 7) {
|
|
647
|
+
r = parseInt(hex.slice(1, 3), 16);
|
|
648
|
+
g = parseInt(hex.slice(3, 5), 16);
|
|
649
|
+
b = parseInt(hex.slice(5, 7), 16);
|
|
650
|
+
}
|
|
651
|
+
r /= 255;
|
|
652
|
+
g /= 255;
|
|
653
|
+
b /= 255;
|
|
654
|
+
let max = Math.max(r, g, b),
|
|
655
|
+
min = Math.min(r, g, b);
|
|
656
|
+
let h,
|
|
657
|
+
s,
|
|
658
|
+
l = (max + min) / 2;
|
|
659
|
+
if (max === min) h = s = 0;
|
|
660
|
+
else {
|
|
661
|
+
let d = max - min;
|
|
662
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
663
|
+
switch (max) {
|
|
664
|
+
case r:
|
|
665
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
666
|
+
break;
|
|
667
|
+
case g:
|
|
668
|
+
h = (b - r) / d + 2;
|
|
669
|
+
break;
|
|
670
|
+
case b:
|
|
671
|
+
h = (r - g) / d + 4;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
h /= 6;
|
|
675
|
+
}
|
|
676
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(
|
|
677
|
+
l * 100
|
|
678
|
+
)}%`;
|
|
679
|
+
}
|
|
680
|
+
|
|
644
681
|
function mapToSqlType(type) {
|
|
645
682
|
const typeMap = {
|
|
646
683
|
uuid: "UUID PRIMARY KEY DEFAULT gen_random_uuid()",
|
|
@@ -686,7 +723,10 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
686
723
|
);
|
|
687
724
|
|
|
688
725
|
// Create output directory structure
|
|
689
|
-
|
|
726
|
+
// If outputDir already ends with config.name, don't nest again
|
|
727
|
+
const projectDir = outputDir.endsWith(config.name)
|
|
728
|
+
? outputDir
|
|
729
|
+
: path.join(outputDir, config.name);
|
|
690
730
|
ensureDir(projectDir);
|
|
691
731
|
|
|
692
732
|
// ========== Generate Structure ==========
|
|
@@ -751,8 +791,11 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
751
791
|
|
|
752
792
|
fs.writeFileSync(path.join(projectDir, componentPath), content.trim());
|
|
753
793
|
|
|
754
|
-
// Generate unit test
|
|
755
|
-
const testPath = `components/${component}.test.tsx`;
|
|
794
|
+
// Generate unit test in tests/ directory
|
|
795
|
+
const testPath = `tests/components/${component}.test.tsx`;
|
|
796
|
+
const testDir = path.dirname(path.join(projectDir, testPath));
|
|
797
|
+
ensureDir(testDir);
|
|
798
|
+
|
|
756
799
|
fs.writeFileSync(
|
|
757
800
|
path.join(projectDir, testPath),
|
|
758
801
|
generateComponentTest(component).trim()
|
|
@@ -771,7 +814,12 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
771
814
|
if (config.api) {
|
|
772
815
|
const spinner3 = ora("Generating API routes...").start();
|
|
773
816
|
config.api.forEach((endpoint) => {
|
|
774
|
-
|
|
817
|
+
// Fix potential /api/api double nesting
|
|
818
|
+
const cleanPath = endpoint.path.startsWith("/api")
|
|
819
|
+
? endpoint.path.slice(4)
|
|
820
|
+
: endpoint.path;
|
|
821
|
+
|
|
822
|
+
const apiPath = `app/api${cleanPath}/route.ts`;
|
|
775
823
|
const apiDir = path.dirname(path.join(projectDir, apiPath));
|
|
776
824
|
ensureDir(apiDir);
|
|
777
825
|
|
|
@@ -820,6 +868,13 @@ async function scaffold(ebadePath, outputDir) {
|
|
|
820
868
|
);
|
|
821
869
|
log.file("tailwind.config.js");
|
|
822
870
|
|
|
871
|
+
// postcss.config.js
|
|
872
|
+
fs.writeFileSync(
|
|
873
|
+
path.join(projectDir, "postcss.config.js"),
|
|
874
|
+
generatePostcssConfig()
|
|
875
|
+
);
|
|
876
|
+
log.file("postcss.config.js");
|
|
877
|
+
|
|
823
878
|
// vitest.config.ts
|
|
824
879
|
fs.writeFileSync(
|
|
825
880
|
path.join(projectDir, "vitest.config.ts"),
|
|
@@ -990,13 +1045,14 @@ async function verifyOutput(projectDir, config) {
|
|
|
990
1045
|
}
|
|
991
1046
|
|
|
992
1047
|
// 3. Test Coverage Check
|
|
993
|
-
// Ensure every component has a matching test file
|
|
1048
|
+
// Ensure every component has a matching test file in tests/components/
|
|
994
1049
|
const components = fs
|
|
995
1050
|
.readdirSync(path.join(projectDir, "components"))
|
|
996
1051
|
.filter((f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx"));
|
|
997
1052
|
components.forEach((comp) => {
|
|
998
1053
|
const testFile = comp.replace(".tsx", ".test.tsx");
|
|
999
|
-
|
|
1054
|
+
const testPath = path.join(projectDir, "tests/components", testFile);
|
|
1055
|
+
if (!fs.existsSync(testPath)) {
|
|
1000
1056
|
results.tests = false;
|
|
1001
1057
|
results.issues.push(`Missing test for component: ${comp}`);
|
|
1002
1058
|
}
|
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.5",
|
|
4
|
+
"description": "MCP Server for ebade v0.4.4 - The Agent-First Framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|