mycontext-cli 0.2.38 → 0.4.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.
Files changed (58) hide show
  1. package/dist/agents/implementations/CodeGenSubAgent.d.ts +2 -0
  2. package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
  3. package/dist/agents/implementations/CodeGenSubAgent.js +344 -24
  4. package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
  5. package/dist/agents/implementations/DocsSubAgent.js +1 -1
  6. package/dist/agents/implementations/DocsSubAgent.js.map +1 -1
  7. package/dist/agents/implementations/EnhancementAgent.d.ts.map +1 -1
  8. package/dist/agents/implementations/EnhancementAgent.js +31 -44
  9. package/dist/agents/implementations/EnhancementAgent.js.map +1 -1
  10. package/dist/agents/implementations/QASubAgent.js +1 -1
  11. package/dist/agents/orchestrator/SubAgentOrchestrator.js +1 -1
  12. package/dist/agents/orchestrator/SubAgentOrchestrator.js.map +1 -1
  13. package/dist/cli.js +38 -2
  14. package/dist/cli.js.map +1 -1
  15. package/dist/commands/auth.d.ts +8 -1
  16. package/dist/commands/auth.d.ts.map +1 -1
  17. package/dist/commands/auth.js +63 -22
  18. package/dist/commands/auth.js.map +1 -1
  19. package/dist/commands/core.d.ts +12 -0
  20. package/dist/commands/core.d.ts.map +1 -0
  21. package/dist/commands/core.js +133 -0
  22. package/dist/commands/core.js.map +1 -0
  23. package/dist/commands/enhance.d.ts +1 -1
  24. package/dist/commands/enhance.d.ts.map +1 -1
  25. package/dist/commands/enhance.js +54 -59
  26. package/dist/commands/enhance.js.map +1 -1
  27. package/dist/commands/generate-components.d.ts +22 -0
  28. package/dist/commands/generate-components.d.ts.map +1 -1
  29. package/dist/commands/generate-components.js +449 -69
  30. package/dist/commands/generate-components.js.map +1 -1
  31. package/dist/commands/generate.d.ts +5 -0
  32. package/dist/commands/generate.d.ts.map +1 -1
  33. package/dist/commands/generate.js +277 -35
  34. package/dist/commands/generate.js.map +1 -1
  35. package/dist/commands/init.js +1 -1
  36. package/dist/commands/init.js.map +1 -1
  37. package/dist/commands/list.d.ts.map +1 -1
  38. package/dist/commands/list.js +25 -9
  39. package/dist/commands/list.js.map +1 -1
  40. package/dist/config/ai-providers.json +25 -0
  41. package/dist/utils/apiKeyManager.d.ts +1 -1
  42. package/dist/utils/apiKeyManager.js +1 -1
  43. package/dist/utils/errorHandler.d.ts +4 -0
  44. package/dist/utils/errorHandler.d.ts.map +1 -1
  45. package/dist/utils/errorHandler.js +4 -0
  46. package/dist/utils/errorHandler.js.map +1 -1
  47. package/dist/utils/fileSystem.d.ts.map +1 -1
  48. package/dist/utils/fileSystem.js +5 -7
  49. package/dist/utils/fileSystem.js.map +1 -1
  50. package/dist/utils/hybridAIClient.d.ts +1 -0
  51. package/dist/utils/hybridAIClient.d.ts.map +1 -1
  52. package/dist/utils/hybridAIClient.js +13 -0
  53. package/dist/utils/hybridAIClient.js.map +1 -1
  54. package/dist/utils/xaiClient.d.ts +19 -0
  55. package/dist/utils/xaiClient.d.ts.map +1 -0
  56. package/dist/utils/xaiClient.js +108 -0
  57. package/dist/utils/xaiClient.js.map +1 -0
  58. package/package.json +8 -2
@@ -40,40 +40,10 @@ exports.GenerateComponentsCommand = void 0;
40
40
  const fileSystem_1 = require("../utils/fileSystem");
41
41
  const spinner_1 = require("../utils/spinner");
42
42
  const chalk_1 = __importDefault(require("chalk"));
43
+ const prompts_1 = __importDefault(require("prompts"));
43
44
  const fs = __importStar(require("fs-extra"));
44
45
  const path = __importStar(require("path"));
45
46
  const child_process_1 = require("child_process");
46
- // Code Generation Sub-Agent
47
- class CodeGenSubAgent {
48
- constructor() {
49
- this.name = "CodeGenSubAgent";
50
- }
51
- async run({ component, group, options }) {
52
- // Use the existing generateComponentCode logic
53
- return new GenerateComponentsCommand().generateComponentCode(component, group, options);
54
- }
55
- }
56
- // QA Sub-Agent (stub for demonstration)
57
- class QASubAgent {
58
- constructor() {
59
- this.name = "QASubAgent";
60
- }
61
- async run({ code, component }) {
62
- // Placeholder: In real implementation, run static analysis, lint, or type checks
63
- // For now, always return true (pass)
64
- return true;
65
- }
66
- }
67
- // Docs Writer Sub-Agent (stub for demonstration)
68
- class DocsSubAgent {
69
- constructor() {
70
- this.name = "DocsSubAgent";
71
- }
72
- async run({ component, group }) {
73
- // Placeholder: Generate markdown or JSDoc for the component
74
- return `# ${component.name}\n\n${component.description}\n`;
75
- }
76
- }
77
47
  // --- Orchestration in GenerateComponentsCommand ---
78
48
  class GenerateComponentsCommand {
79
49
  constructor() {
@@ -117,6 +87,8 @@ class GenerateComponentsCommand {
117
87
  async execute(target, options) {
118
88
  const spinner = new spinner_1.EnhancedSpinner("Initializing component generation...");
119
89
  try {
90
+ // System reminder: high-level plan
91
+ console.log(chalk_1.default.gray("\n[mycontext] Plan: plan → generate → QA → docs → preview (→ checks)\n"));
120
92
  // Check authentication unless local mode is enabled
121
93
  let userInfo = null;
122
94
  if (!options.local) {
@@ -146,6 +118,36 @@ class GenerateComponentsCommand {
146
118
  // Determine if we're generating a specific group or all components
147
119
  const isAll = target === "all" || options.all;
148
120
  const groupName = isAll ? undefined : target;
121
+ // Core-first flow: generate a single BrandComp then exit
122
+ if (isAll && options.coreFirst) {
123
+ await this.generateCoreBrandComp(options, spinner);
124
+ // Update preview registry and ensure /preview route
125
+ if (options.updatePreview !== false) {
126
+ const componentsDir = options.output || path.join("components", ".mycontext");
127
+ await this.updatePreviewRegistry(componentsDir);
128
+ await this.ensurePreviewRoute();
129
+ }
130
+ // Guidance for refinement step
131
+ console.log(chalk_1.default.blue("\n🧩 Core-first step complete: edit 'components/.mycontext/core/BrandComp.tsx' until you're happy."));
132
+ console.log(chalk_1.default.gray(" You can also run: mycontext enhance components/.mycontext/core/BrandComp.tsx --prompt 'Tweak spacing/colors'\n"));
133
+ // If non-interactive, stop here
134
+ if (options.yes) {
135
+ return;
136
+ }
137
+ // Ask whether to proceed with remaining components now
138
+ const answer = await (0, prompts_1.default)({
139
+ type: "toggle",
140
+ name: "proceed",
141
+ message: "Proceed to generate the remaining components now? (You can rerun later)",
142
+ initial: false,
143
+ active: "yes",
144
+ inactive: "no",
145
+ });
146
+ if (answer?.proceed) {
147
+ await this.generateAllComponents(options, spinner, userInfo.userId);
148
+ }
149
+ return;
150
+ }
149
151
  if (isAll) {
150
152
  await this.generateAllComponents(options, spinner, userInfo.userId);
151
153
  }
@@ -239,7 +241,9 @@ class GenerateComponentsCommand {
239
241
  const list = this.contextArtifacts.compList;
240
242
  if (!list || !Array.isArray(list.groups))
241
243
  return null;
242
- const norm = (s) => String(s || "").toLowerCase().replace(/\s+/g, " ");
244
+ const norm = (s) => String(s || "")
245
+ .toLowerCase()
246
+ .replace(/\s+/g, " ");
243
247
  const g = list.groups.find((x) => norm(x?.name) === norm(groupName));
244
248
  if (!g || !Array.isArray(g.components))
245
249
  return null;
@@ -270,6 +274,8 @@ class GenerateComponentsCommand {
270
274
  }
271
275
  // Ensure shadcn/ui components are available before generation
272
276
  await this.ensureShadcnComponentsInstalled(groups, spinner);
277
+ // Ensure form dependencies (zod, react-hook-form) exist
278
+ await this.ensureFormDeps(spinner);
273
279
  // Ensure test scaffold exists (optional)
274
280
  if (options.withTests) {
275
281
  await this.ensureTestsScaffold(spinner);
@@ -281,6 +287,7 @@ class GenerateComponentsCommand {
281
287
  let generatedGroups = 0;
282
288
  for (const group of groups) {
283
289
  spinner.updateText(`Generating ${group.name} components...`);
290
+ console.log(chalk_1.default.gray(`\n[mycontext] Reminder: generate → QA → docs for group '${group.name}'`));
284
291
  const groupDir = path.join(componentsDir, this.toKebabCase(group.name));
285
292
  await this.fs.ensureDir(groupDir);
286
293
  const components = group.components || [];
@@ -302,6 +309,8 @@ class GenerateComponentsCommand {
302
309
  await this.updatePreviewRegistry(componentsDir);
303
310
  await this.ensurePreviewRoute();
304
311
  }
312
+ // Post-generation: scan actual imports and ensure missing shadcn primitives are installed
313
+ await this.scanAndInstallShadcnFromComponents(componentsDir, spinner);
305
314
  spinner.success({
306
315
  text: `Generated ${totalComponents} components across ${generatedGroups} groups!`,
307
316
  });
@@ -318,6 +327,21 @@ class GenerateComponentsCommand {
318
327
  });
319
328
  // Post-run hints
320
329
  this.printNextStepsAfterComponents(options);
330
+ // Optionally open preview in browser
331
+ if (options.openPreview !== false) {
332
+ try {
333
+ const url = "http://localhost:3000/preview";
334
+ console.log(chalk_1.default.blue(`\n🌐 Opening preview: ${url}`));
335
+ // best-effort open using the OS default opener
336
+ const opener = process.platform === "darwin"
337
+ ? "open"
338
+ : process.platform === "win32"
339
+ ? "start"
340
+ : "xdg-open";
341
+ (0, child_process_1.execSync)(`${opener} ${url}`, { stdio: "ignore" });
342
+ }
343
+ catch { }
344
+ }
321
345
  }
322
346
  async generateComponentGroup(groupName, options, spinner, userId) {
323
347
  spinner.updateText(`Generating ${groupName} components...`);
@@ -340,6 +364,8 @@ class GenerateComponentsCommand {
340
364
  }
341
365
  // Ensure shadcn/ui components are available before generation
342
366
  await this.ensureShadcnComponentsInstalled([group], spinner);
367
+ // Ensure form dependencies (zod, react-hook-form) exist
368
+ await this.ensureFormDeps(spinner);
343
369
  // Ensure test scaffold exists (optional)
344
370
  if (options.withTests) {
345
371
  await this.ensureTestsScaffold(spinner);
@@ -364,6 +390,8 @@ class GenerateComponentsCommand {
364
390
  await this.updatePreviewRegistry(compBaseDir);
365
391
  await this.ensurePreviewRoute();
366
392
  }
393
+ // Post-generation: scan actual imports and ensure missing shadcn primitives are installed
394
+ await this.scanAndInstallShadcnFromComponents(compBaseDir, spinner);
367
395
  spinner.success({
368
396
  text: `Generated ${components.length} components in ${group.name}!`,
369
397
  });
@@ -374,6 +402,204 @@ class GenerateComponentsCommand {
374
402
  });
375
403
  console.log(chalk_1.default.gray(` • index.ts`));
376
404
  console.log(chalk_1.default.gray(` • page.tsx`));
405
+ // Optionally open preview in browser
406
+ if (options.openPreview !== false) {
407
+ try {
408
+ const url = "http://localhost:3000/preview";
409
+ console.log(chalk_1.default.blue(`\n🌐 Opening preview: ${url}`));
410
+ const opener = process.platform === "darwin"
411
+ ? "open"
412
+ : process.platform === "win32"
413
+ ? "start"
414
+ : "xdg-open";
415
+ (0, child_process_1.execSync)(`${opener} ${url}`, { stdio: "ignore" });
416
+ }
417
+ catch { }
418
+ }
419
+ }
420
+ /**
421
+ * Generate a single core BrandComp using PRD/types/brand context to act as the design anchor.
422
+ */
423
+ async generateCoreBrandComp(options, spinner) {
424
+ spinner.updateText("Generating core BrandComp...");
425
+ const componentsDir = options.output || path.join("components", ".mycontext");
426
+ const groupDir = path.join(componentsDir, "core");
427
+ await this.fs.ensureDir(groupDir);
428
+ // Ensure shadcn initialized
429
+ await this.ensureShadcnComponentsInstalled([
430
+ {
431
+ name: "Core",
432
+ components: [
433
+ {
434
+ name: "BrandComp",
435
+ type: "layout",
436
+ description: "Core brand canvas",
437
+ tags: ["layout", "canvas"],
438
+ },
439
+ ],
440
+ },
441
+ ], spinner);
442
+ // Best-effort brand token application
443
+ await this.applyBrandTokens();
444
+ const brandComp = {
445
+ name: "BrandComp",
446
+ type: "layout",
447
+ description: "A core brand canvas showcasing typography, primary/secondary colors, interactive states, and spacing. Acts as a reference for other components.",
448
+ userStories: [
449
+ "As a designer, I can see brand colors and typography applied consistently.",
450
+ "As a developer, I can reference spacing, radius, and interactive states.",
451
+ ],
452
+ actionFunctions: [],
453
+ dependencies: ["react"],
454
+ tags: ["brand", "layout", "canvas"],
455
+ };
456
+ // Try sub-agent generation first
457
+ try {
458
+ const { orchestrator } = await Promise.resolve().then(() => __importStar(require("../agents/orchestrator/SubAgentOrchestrator")));
459
+ const codeResult = (await orchestrator.executeAgent("CodeGenSubAgent", {
460
+ component: brandComp,
461
+ group: { name: "Core" },
462
+ options: {
463
+ ...options,
464
+ context: {
465
+ prd: this.contextArtifacts.prd,
466
+ types: this.contextArtifacts.types,
467
+ },
468
+ },
469
+ }));
470
+ await this.fs.writeFile(path.join(groupDir, `BrandComp.tsx`), codeResult.code);
471
+ }
472
+ catch {
473
+ const code = this.generateComponentCode(brandComp, { name: "Core" }, {
474
+ ...options,
475
+ context: {
476
+ prd: this.contextArtifacts.prd,
477
+ types: this.contextArtifacts.types,
478
+ },
479
+ });
480
+ await this.fs.writeFile(path.join(groupDir, `BrandComp.tsx`), code);
481
+ }
482
+ // Group index and preview
483
+ await this.generateGroupIndex({
484
+ name: "Core",
485
+ description: "Core brand canvas",
486
+ components: [brandComp],
487
+ }, groupDir);
488
+ await this.generatePreviewPage({
489
+ name: "Core",
490
+ description: "Core brand canvas",
491
+ components: [brandComp],
492
+ }, groupDir);
493
+ spinner.success({ text: "Generated core BrandComp." });
494
+ }
495
+ /**
496
+ * Scan generated TSX files for imports from '@/components/ui/*' and ensure those shadcn primitives are installed.
497
+ */
498
+ async scanAndInstallShadcnFromComponents(componentsBaseDir, spinner) {
499
+ try {
500
+ const projectRoot = process.cwd();
501
+ const pkgJsonPath = path.join(projectRoot, "package.json");
502
+ if (!(await fs.pathExists(pkgJsonPath)))
503
+ return;
504
+ const needed = new Set();
505
+ const walk = async (dir) => {
506
+ const entries = await fs.readdir(dir);
507
+ for (const entry of entries) {
508
+ const full = path.join(dir, entry);
509
+ const stat = await fs.stat(full);
510
+ if (stat.isDirectory())
511
+ await walk(full);
512
+ else if (entry.endsWith(".tsx")) {
513
+ const src = await fs.readFile(full, "utf8");
514
+ const re = /from\s+["']@\/components\/ui\/([^"']+)["']/g;
515
+ let m;
516
+ while ((m = re.exec(src))) {
517
+ const mod = m[1];
518
+ if (mod)
519
+ needed.add(mod);
520
+ }
521
+ }
522
+ }
523
+ };
524
+ await walk(componentsBaseDir);
525
+ if (needed.size === 0)
526
+ return;
527
+ // Filter out already-present ui files
528
+ const uiDir = path.join(projectRoot, "components", "ui");
529
+ const missing = [];
530
+ for (const name of needed) {
531
+ const p = path.join(uiDir, `${name}.tsx`);
532
+ if (!(await fs.pathExists(p)))
533
+ missing.push(name);
534
+ }
535
+ if (missing.length === 0)
536
+ return;
537
+ spinner.updateText(`Installing missing shadcn primitives from imports (${missing.length})...`);
538
+ const pm = await this.detectPackageManager(projectRoot);
539
+ try {
540
+ if (pm === "pnpm") {
541
+ (0, child_process_1.execSync)(`pnpm dlx shadcn@latest add ${missing.join(" ")}`.trim(), {
542
+ cwd: projectRoot,
543
+ stdio: "inherit",
544
+ });
545
+ }
546
+ else {
547
+ (0, child_process_1.execSync)(`npx shadcn@latest add ${missing.join(" ")}`.trim(), {
548
+ cwd: projectRoot,
549
+ stdio: "inherit",
550
+ });
551
+ }
552
+ }
553
+ catch {
554
+ console.log(chalk_1.default.yellow(" ⚠️ shadcn add (post-scan) failed; you can add components manually."));
555
+ }
556
+ }
557
+ catch (error) {
558
+ console.log(chalk_1.default.yellow(` ⚠️ shadcn post-scan encountered an issue: ${error instanceof Error ? error.message : String(error)}`));
559
+ }
560
+ }
561
+ /**
562
+ * Apply brand tokens to globals.css (:root variables) if branding exists.
563
+ */
564
+ async applyBrandTokens() {
565
+ try {
566
+ const projectRoot = process.cwd();
567
+ const appDir = (await fs.pathExists(path.join(projectRoot, "src", "app")))
568
+ ? path.join(projectRoot, "src", "app")
569
+ : path.join(projectRoot, "app");
570
+ const globalsPath = path.join(appDir, "globals.css");
571
+ if (!(await fs.pathExists(globalsPath)))
572
+ return;
573
+ const brandPath = path.join(projectRoot, ".mycontext", "03-branding.md");
574
+ if (!(await fs.pathExists(brandPath)))
575
+ return;
576
+ const brand = await fs.readFile(brandPath, "utf8");
577
+ const pick = (label) => {
578
+ const m = brand.match(new RegExp(label + ".*?(#[0-9a-fA-F]{6})"));
579
+ return m ? m[1] : null;
580
+ };
581
+ const primary = pick("primary") || pick("Primary") || null;
582
+ const secondary = pick("secondary") || pick("Secondary") || null;
583
+ const accent = pick("accent") || pick("Accent") || null;
584
+ if (!primary && !secondary && !accent)
585
+ return;
586
+ let css = await fs.readFile(globalsPath, "utf8");
587
+ const ensureVar = (name, value) => {
588
+ const re = new RegExp(`(--${name}:\s*)([^;]+)(;)`);
589
+ if (re.test(css))
590
+ css = css.replace(re, `$1${value}$3`);
591
+ else
592
+ css = css.replace(/:root\s*\{/, (m) => `${m}\n --${name}: ${value};`);
593
+ };
594
+ if (primary)
595
+ ensureVar("primary", primary);
596
+ if (secondary)
597
+ ensureVar("secondary", secondary);
598
+ if (accent)
599
+ ensureVar("accent", accent);
600
+ await fs.writeFile(globalsPath, css);
601
+ }
602
+ catch { }
377
603
  }
378
604
  async generateComponent(component, group, groupDir, options, userId) {
379
605
  try {
@@ -435,15 +661,23 @@ class GenerateComponentsCommand {
435
661
  .replace(new RegExp(`${safeName} Default`, "g"), `${safeName}Default`);
436
662
  await this.fs.writeFile(componentPath, fixedCode);
437
663
  // Execute QA and docs in parallel
664
+ const componentWithContext = {
665
+ ...component,
666
+ _context: {
667
+ group: group?.name,
668
+ prd: this.contextArtifacts.prd,
669
+ types: this.contextArtifacts.types,
670
+ },
671
+ };
438
672
  const [qaResult, docsResult] = (await Promise.all([
439
673
  orchestrator.executeAgent("QASubAgent", {
440
674
  code: codeResult.code,
441
- component,
675
+ component: componentWithContext,
442
676
  standards: ["typescript", "react", "accessibility"],
443
677
  }),
444
678
  orchestrator.executeAgent("DocsSubAgent", {
445
679
  code: codeResult.code,
446
- component,
680
+ component: componentWithContext,
447
681
  format: "readme",
448
682
  }),
449
683
  ]));
@@ -515,6 +749,11 @@ class GenerateComponentsCommand {
515
749
  async ensureShadcnComponentsInstalled(groups, spinner) {
516
750
  try {
517
751
  const projectRoot = process.cwd();
752
+ const pkgJsonPath = path.join(projectRoot, "package.json");
753
+ if (!(await fs.pathExists(pkgJsonPath))) {
754
+ console.log(chalk_1.default.gray(" Skipping shadcn setup: no package.json in current directory. Run inside an existing Next.js project."));
755
+ return;
756
+ }
518
757
  const componentsJsonPath = path.join(projectRoot, "components.json");
519
758
  // Initialize shadcn if components.json is missing
520
759
  if (!(await fs.pathExists(componentsJsonPath))) {
@@ -551,6 +790,10 @@ class GenerateComponentsCommand {
551
790
  const names = Array.from(needed);
552
791
  spinner.updateText(`Installing shadcn components (${names.length})...`);
553
792
  try {
793
+ if (!(await fs.pathExists(pkgJsonPath))) {
794
+ console.log(chalk_1.default.gray(" Skipping 'shadcn add' because no package.json was found."));
795
+ return;
796
+ }
554
797
  if (pkgManager === "pnpm") {
555
798
  (0, child_process_1.execSync)(`pnpm dlx shadcn@latest add ${names.join(" ")}`, {
556
799
  cwd: projectRoot,
@@ -572,6 +815,50 @@ class GenerateComponentsCommand {
572
815
  console.log(chalk_1.default.yellow(` ⚠️ shadcn setup encountered an issue: ${error instanceof Error ? error.message : String(error)}`));
573
816
  }
574
817
  }
818
+ async ensureFormDeps(spinner) {
819
+ try {
820
+ const projectRoot = process.cwd();
821
+ const pkgPath = path.join(projectRoot, "package.json");
822
+ const pkg = (await fs.pathExists(pkgPath))
823
+ ? await fs.readJson(pkgPath)
824
+ : {};
825
+ const deps = {
826
+ ...(pkg.dependencies || {}),
827
+ ...(pkg.devDependencies || {}),
828
+ };
829
+ const need = [];
830
+ if (!deps["zod"])
831
+ need.push("zod");
832
+ if (!deps["react-hook-form"])
833
+ need.push("react-hook-form");
834
+ if (!deps["@hookform/resolvers"])
835
+ need.push("@hookform/resolvers");
836
+ if (need.length === 0)
837
+ return;
838
+ spinner.updateText(`Ensuring form dependencies: ${need.join(", ")}`);
839
+ const pm = await this.detectPackageManager(projectRoot);
840
+ try {
841
+ if (pm === "pnpm") {
842
+ (0, child_process_1.execSync)(`pnpm add ${need.join(" ")}`, {
843
+ cwd: projectRoot,
844
+ stdio: "inherit",
845
+ });
846
+ }
847
+ else {
848
+ (0, child_process_1.execSync)(`npm i ${need.join(" ")}`, {
849
+ cwd: projectRoot,
850
+ stdio: "inherit",
851
+ });
852
+ }
853
+ }
854
+ catch {
855
+ console.log(chalk_1.default.yellow(" ⚠️ Failed to install form deps automatically. You can install them manually."));
856
+ }
857
+ }
858
+ catch (error) {
859
+ console.log(chalk_1.default.yellow(` ⚠️ Form deps step encountered an issue: ${error instanceof Error ? error.message : String(error)}`));
860
+ }
861
+ }
575
862
  inferShadcnForComponent(component, out) {
576
863
  const type = (component.type || "").toLowerCase();
577
864
  const name = String(component.name || "").toLowerCase();
@@ -608,7 +895,7 @@ class GenerateComponentsCommand {
608
895
  /alert|dialog|toast|progress|skeleton/.test(name)) {
609
896
  add(["alert", "alert-dialog", "dialog", "progress", "skeleton"]);
610
897
  }
611
- if (type === "data" || /table|command|combobox/.test(name)) {
898
+ if (type === "data" || /table|command|combobox|list/.test(name)) {
612
899
  add(["table", "command"]);
613
900
  }
614
901
  if (type === "overlay" || /popover|tooltip|sheet|drawer/.test(name)) {
@@ -1008,7 +1295,6 @@ export default ${name};
1008
1295
  e.preventDefault();
1009
1296
  setIsSubmitting(true);
1010
1297
  try {
1011
- // TODO: Implement form submission logic
1012
1298
  onSubmit?.(formData);
1013
1299
  } catch (error) {
1014
1300
  console.error("Form submission error:", error);
@@ -1050,42 +1336,19 @@ export default ${name};
1050
1336
  switch (normalizedType) {
1051
1337
  case "form":
1052
1338
  return `
1339
+ {/* Example domain-agnostic form scaffold. Replace fields per component plan. */}
1053
1340
  <form onSubmit={handleSubmit} className="space-y-4">
1054
1341
  <div className="space-y-2">
1055
- <label htmlFor="email" className="text-sm font-medium">
1056
- Email
1057
- </label>
1058
- <input
1059
- id="email"
1060
- type="email"
1061
- className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
1062
- placeholder="Enter your email"
1063
- required
1064
- />
1342
+ <label htmlFor="field1" className="text-sm font-medium">Field 1</label>
1343
+ <input id="field1" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" placeholder="Type here" />
1065
1344
  </div>
1066
- <div className="space-y-2">
1067
- <label htmlFor="password" className="text-sm font-medium">
1068
- Password
1069
- </label>
1070
- <input
1071
- id="password"
1072
- type="password"
1073
- className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
1074
- placeholder="Enter your password"
1075
- required
1076
- />
1077
- </div>
1078
- {error && (
1079
- <div className="text-sm text-destructive">
1080
- {error}
1081
- </div>
1082
- )}
1345
+ {error ? <div className="text-sm text-destructive">{error}</div> : null}
1083
1346
  <button
1084
1347
  type="submit"
1085
1348
  disabled={loading || isSubmitting}
1086
1349
  className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
1087
1350
  >
1088
- {loading || isSubmitting ? "Loading..." : "Submit"}
1351
+ {loading || isSubmitting ? "Submitting..." : "Submit"}
1089
1352
  </button>
1090
1353
  </form>`;
1091
1354
  case "layout":
@@ -1146,14 +1409,14 @@ export default ${name};
1146
1409
  case "handleLogin":
1147
1410
  return `
1148
1411
  export async function handleLogin(email: string, password: string) {
1149
- // TODO: Implement login logic
1412
+ // Implement login logic in application layer
1150
1413
  console.log("Logging in with:", { email, password });
1151
1414
  return { success: true };
1152
1415
  }`;
1153
1416
  case "handleSignup":
1154
1417
  return `
1155
1418
  export async function handleSignup(email: string, password: string, name: string) {
1156
- // TODO: Implement signup logic
1419
+ // Implement signup logic in application layer
1157
1420
  console.log("Signing up with:", { email, password, name });
1158
1421
  return { success: true };
1159
1422
  }`;
@@ -1166,14 +1429,14 @@ export function validateEmail(email: string): boolean {
1166
1429
  case "checkUsername":
1167
1430
  return `
1168
1431
  export async function checkUsername(username: string): Promise<boolean> {
1169
- // TODO: Implement username availability check
1432
+ // Implement username availability check in application layer
1170
1433
  console.log("Checking username:", username);
1171
1434
  return true;
1172
1435
  }`;
1173
1436
  default:
1174
1437
  return `
1175
1438
  export function ${func}() {
1176
- // TODO: Implement ${func} logic
1439
+ // Implement ${func} logic in application layer
1177
1440
  console.log("${func} called");
1178
1441
  }`;
1179
1442
  }
@@ -1264,6 +1527,8 @@ ${components
1264
1527
  .join(" ");
1265
1528
  groups.push({ dir: item, name: title, files });
1266
1529
  }
1530
+ // Build preview props based on component prop interfaces and types
1531
+ await this.buildPreviewProps(componentsDir);
1267
1532
  const registryPath = path.join(componentsDir, "registry.tsx");
1268
1533
  const entries = [];
1269
1534
  for (const g of groups) {
@@ -1276,6 +1541,7 @@ ${components
1276
1541
  const content = `"use client";
1277
1542
  import React from 'react';
1278
1543
  import dynamic from 'next/dynamic';
1544
+ import { previewProps } from './preview-props';
1279
1545
 
1280
1546
  export type PreviewRegistryItem = {
1281
1547
  group: string;
@@ -1305,7 +1571,8 @@ export const previewItems = items.map((item) => ({
1305
1571
  if (!comp) {
1306
1572
  console.warn('[mycontext/preview] Missing export ' + item.name + ' in ' + item.path);
1307
1573
  }
1308
- return (comp || ((props: any) => <MissingComponent name={item.name} {...props} />)) as React.ComponentType<any>;
1574
+ const C = (comp || ((props: any) => <MissingComponent name={item.name} {...props} />)) as React.ComponentType<any>;
1575
+ return (props: any) => <C {...(previewProps[item.name] || {})} {...props} />;
1309
1576
  } catch (e) {
1310
1577
  console.warn('[mycontext/preview] Failed to load ' + item.path + ':', e);
1311
1578
  return ((props: any) => <MissingComponent name={item.name} {...props} />) as React.ComponentType<any>;
@@ -1368,6 +1635,119 @@ function groupBy<T, K extends string | number>(
1368
1635
  console.log(chalk_1.default.yellow(` ⚠️ Failed to update preview registry: ${error instanceof Error ? error.message : String(error)}`));
1369
1636
  }
1370
1637
  }
1638
+ /**
1639
+ * Build preview-props.ts by parsing component prop interfaces and .mycontext/02-types.ts
1640
+ * Generates sample values for primitives and known interfaces; skips unknowns.
1641
+ */
1642
+ async buildPreviewProps(componentsDir) {
1643
+ try {
1644
+ const projectRoot = process.cwd();
1645
+ const typesPathCandidates = [
1646
+ path.join(projectRoot, ".mycontext", "02-types.ts"),
1647
+ path.join(projectRoot, ".mycontext", "types.ts"),
1648
+ path.join(projectRoot, "context", "types.ts"),
1649
+ ];
1650
+ let typesSource = "";
1651
+ for (const p of typesPathCandidates) {
1652
+ if (await fs.pathExists(p)) {
1653
+ typesSource = await fs.readFile(p, "utf8");
1654
+ break;
1655
+ }
1656
+ }
1657
+ // Parse interfaces from types file
1658
+ const interfaceRegex = /interface\s+(\w+)\s*\{([\s\S]*?)\}/g;
1659
+ const interfaces = {};
1660
+ let match;
1661
+ while ((match = interfaceRegex.exec(typesSource))) {
1662
+ const name = match[1];
1663
+ const body = match[2];
1664
+ const fields = {};
1665
+ body
1666
+ .split("\n")
1667
+ .map((l) => l.trim())
1668
+ .filter(Boolean)
1669
+ .forEach((line) => {
1670
+ const m = line.match(/^(\w+)\??:\s*([^;]+);/);
1671
+ if (m)
1672
+ fields[m[1]] = m[2].trim();
1673
+ });
1674
+ interfaces[name] = fields;
1675
+ }
1676
+ // Discover components and parse their Props interfaces
1677
+ const propsMap = {};
1678
+ const groups = await fs.readdir(componentsDir);
1679
+ for (const g of groups) {
1680
+ const groupPath = path.join(componentsDir, g);
1681
+ const stat = await fs.stat(groupPath);
1682
+ if (!stat.isDirectory())
1683
+ continue;
1684
+ const files = (await fs.readdir(groupPath)).filter((f) => f.endsWith(".tsx"));
1685
+ for (const file of files) {
1686
+ if (file === "page.tsx")
1687
+ continue;
1688
+ const base = file.replace(/\.tsx$/, "");
1689
+ const full = path.join(groupPath, file);
1690
+ const src = await fs.readFile(full, "utf8");
1691
+ const propsInterfaceMatch = src.match(/interface\s+(\w+)Props\s*\{([\s\S]*?)\}/);
1692
+ if (!propsInterfaceMatch)
1693
+ continue;
1694
+ const propsBody = propsInterfaceMatch[2];
1695
+ const entries = propsBody
1696
+ .split("\n")
1697
+ .map((l) => l.trim())
1698
+ .filter((l) => /:\s*/.test(l));
1699
+ const propsObj = {};
1700
+ for (const line of entries) {
1701
+ const m = line.match(/^(\w+)\??:\s*([^;]+);/);
1702
+ if (!m)
1703
+ continue;
1704
+ const propName = m[1];
1705
+ const typeStr = m[2].trim();
1706
+ const value = this.generateSampleValue(typeStr, interfaces, 0);
1707
+ if (value !== undefined)
1708
+ propsObj[propName] = value;
1709
+ }
1710
+ if (Object.keys(propsObj).length > 0)
1711
+ propsMap[base] = propsObj;
1712
+ }
1713
+ }
1714
+ const out = `export const previewProps: Record<string, any> = ${JSON.stringify(propsMap, null, 2)};\n`;
1715
+ await fs.writeFile(path.join(componentsDir, "preview-props.ts"), out);
1716
+ }
1717
+ catch (error) {
1718
+ console.log(chalk_1.default.yellow(` ⚠️ Failed to build preview props: ${error instanceof Error ? error.message : String(error)}`));
1719
+ }
1720
+ }
1721
+ generateSampleValue(typeStr, interfaces, depth) {
1722
+ if (depth > 2)
1723
+ return undefined;
1724
+ const t = typeStr.replace(/\s+/g, "");
1725
+ if (t.endsWith("[]")) {
1726
+ const inner = t.slice(0, -2);
1727
+ const v = this.generateSampleValue(inner, interfaces, depth + 1);
1728
+ return v === undefined ? undefined : [v];
1729
+ }
1730
+ if (/^string\b/.test(t))
1731
+ return "Sample";
1732
+ if (/^number\b/.test(t))
1733
+ return 1;
1734
+ if (/^boolean\b/.test(t))
1735
+ return true;
1736
+ if (/^Date\b/.test(t))
1737
+ return new Date().toISOString();
1738
+ if (interfaces[t]) {
1739
+ const fields = interfaces[t];
1740
+ const obj = {};
1741
+ for (const [k, vt] of Object.entries(fields)) {
1742
+ const v = this.generateSampleValue(vt, interfaces, depth + 1);
1743
+ if (v !== undefined)
1744
+ obj[k] = v;
1745
+ }
1746
+ return obj;
1747
+ }
1748
+ // Unknown type: skip
1749
+ return undefined;
1750
+ }
1371
1751
  async ensurePreviewRoute() {
1372
1752
  try {
1373
1753
  const projectRoot = process.cwd();