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 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.3${colors.reset}
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: Placeholder (No template found in cli/templates)
99
+ * Status: Intent needs implementation
100
100
  */
101
101
  export function ${toPascalCase(componentName)}() {
102
102
  return (
103
- <div className="p-12 border-2 border-dashed border-border rounded-3xl text-center bg-muted/30">
104
- <div className="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
105
- <span className="text-2xl">🧩</span>
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-xl font-bold mb-2">${toPascalCase(componentName)}</h3>
108
- <p className="text-sm text-muted-foreground max-w-xs mx-auto">
109
- No template found for this intent. Create a file at <code>cli/templates/${componentName}.tsx</code> to customize.
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
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
181
- <header className="mb-12">
182
- <h1 className="text-3xl font-bold tracking-tight opacity-90">${toPascalCase(
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
- <main className="space-y-12">
186
+ <div className="relative z-10">
189
187
  ${componentUsage}
190
- </main>
191
- </div>
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: 0 0% 100%;
463
- --foreground: 222.2 84% 4.9%;
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.2 84% 4.9%;
498
- --card-foreground: 210 40% 98%;
473
+ --card: 222 47% 4%;
474
+ --card-foreground: 213 31% 91%;
499
475
 
500
- --popover: 222.2 84% 4.9%;
501
- --popover-foreground: 210 40% 98%;
476
+ --popover: 222 47% 4%;
477
+ --popover-foreground: 213 31% 91%;
502
478
 
503
- --primary: 217.2 91.2% 59.8%;
504
- --primary-foreground: 222.2 47.4% 11.2%;
479
+ --primary: ${hexToHsl(primary)};
480
+ --primary-foreground: 0 0% 100%;
505
481
 
506
- --secondary: 217.2 32.6% 17.5%;
507
- --secondary-foreground: 210 40% 98%;
482
+ --secondary: 222 47% 11%;
483
+ --secondary-foreground: 213 31% 91%;
508
484
 
509
- --muted: 217.2 32.6% 17.5%;
510
- --muted-foreground: 215 20.2% 65.1%;
485
+ --muted: 223 47% 11%;
486
+ --muted-foreground: 215.4 16.3% 56.9%;
511
487
 
512
- --accent: 217.2 32.6% 17.5%;
488
+ --accent: 216 34% 17%;
513
489
  --accent-foreground: 210 40% 98%;
514
490
 
515
- --destructive: 0 62.8% 30.6%;
491
+ --destructive: 0 63% 31%;
516
492
  --destructive-foreground: 210 40% 98%;
517
493
 
518
- --border: 217.2 32.6% 17.5%;
519
- --input: 217.2 32.6% 17.5%;
520
- --ring: 224.3 76.3% 48%;
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(/([A-Z])/g, "_$1")
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
- const projectDir = path.join(outputDir, config.name);
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
- const apiPath = `app/api${endpoint.path}/route.ts`;
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
- if (!fs.existsSync(path.join(projectDir, "components", testFile))) {
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,6 +1,6 @@
1
1
  {
2
2
  "name": "ebade",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "ebade - Agent-First Framework. The first framework designed for AI agents, readable by humans.",
5
5
  "type": "module",
6
6
  "main": "cli/scaffold.js",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ebade-mcp-server",
3
- "version": "0.4.3",
4
- "description": "MCP Server for ebade v0.4.3 - The Agent-First Framework",
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": {
package/www/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "www",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",