@getcoherent/cli 0.6.26 → 0.6.27

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.
@@ -0,0 +1,2468 @@
1
+ import {
2
+ ensureUseClientIfNeeded,
3
+ findMissingPackagesInCode,
4
+ fixEscapedClosingQuotes,
5
+ fixUnescapedLtInJsx,
6
+ getPageGroup,
7
+ installPackages,
8
+ sanitizeMetadataStrings
9
+ } from "./chunk-VNDQ4DYQ.js";
10
+
11
+ // src/commands/chat/code-generator.ts
12
+ import { resolve as resolve3 } from "path";
13
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3 } from "fs";
14
+ import { mkdir as mkdir3 } from "fs/promises";
15
+ import { dirname as dirname3 } from "path";
16
+ import {
17
+ ComponentGenerator,
18
+ PageGenerator,
19
+ TailwindConfigGenerator
20
+ } from "@getcoherent/core";
21
+
22
+ // src/agents/page-templates.ts
23
+ var PAGE_TEMPLATES = {
24
+ dashboard: {
25
+ description: "Dashboard page with KPI stats grid and recent activity",
26
+ sections: [
27
+ 'Page header: h1 "Dashboard" with className="text-2xl font-bold tracking-tight" and a p with className="text-sm text-muted-foreground" subtitle like "Overview of your key metrics and recent activity"',
28
+ '4 stat cards in a grid (className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"). Each card uses Card > CardHeader(className="flex flex-row items-center justify-between space-y-0 pb-2") > CardTitle(className="text-sm font-medium") + lucide icon(className="size-4 text-muted-foreground") ; CardContent > metric value(className="text-2xl font-bold") + change text(className="text-xs text-muted-foreground"). Stats: Total Revenue ($45,231.89, +20.1%), Active Users (2,350, +180 since last hour), Sales (12,234, +19% from last month), Active Now (573, +201 since last hour)',
29
+ 'Recent activity Card with CardHeader > CardTitle "Recent Activity" (text-sm font-medium) + CardDescription (text-sm text-muted-foreground). CardContent with a list of 5 activity items, each with title (text-sm font-medium), description (text-sm text-muted-foreground), and time (text-sm text-muted-foreground). Use flex items-center justify-between for each row.'
30
+ ],
31
+ components: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent"]
32
+ },
33
+ login: {
34
+ description: "Login page with centered card form",
35
+ sections: [
36
+ 'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
37
+ 'Card with CardHeader: CardTitle "Sign in" (text-2xl font-bold), CardDescription "Enter your credentials to access your account" (text-sm text-muted-foreground).',
38
+ 'CardContent with form: email Input (type="email", placeholder="you@example.com"), password Input (type="password"), a "Forgot password?" link (text-sm text-muted-foreground hover:text-foreground), and a Button "Sign in" (w-full).',
39
+ `CardFooter: text "Don't have an account?" with a Sign up link. All text is text-sm text-muted-foreground.`,
40
+ 'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
41
+ ],
42
+ components: [
43
+ "Card",
44
+ "CardHeader",
45
+ "CardTitle",
46
+ "CardDescription",
47
+ "CardContent",
48
+ "CardFooter",
49
+ "Button",
50
+ "Input",
51
+ "Label"
52
+ ]
53
+ },
54
+ register: {
55
+ description: "Registration page with centered card form",
56
+ sections: [
57
+ 'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
58
+ 'Card with CardHeader: CardTitle "Create an account" (text-2xl font-bold), CardDescription "Enter your details to get started" (text-sm text-muted-foreground).',
59
+ 'CardContent with form: name Input, email Input (type="email"), password Input (type="password"), confirm password Input (type="password"), and a Button "Create account" (w-full).',
60
+ 'CardFooter: text "Already have an account?" with a Sign in link. All text is text-sm text-muted-foreground.',
61
+ 'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
62
+ ],
63
+ components: [
64
+ "Card",
65
+ "CardHeader",
66
+ "CardTitle",
67
+ "CardDescription",
68
+ "CardContent",
69
+ "CardFooter",
70
+ "Button",
71
+ "Input",
72
+ "Label"
73
+ ]
74
+ },
75
+ pricing: {
76
+ description: "Pricing page with tier comparison cards",
77
+ sections: [
78
+ 'Page header: h1 "Pricing Plans" className="text-2xl font-bold tracking-tight" + p "Choose the perfect plan for your needs" className="text-sm text-muted-foreground"',
79
+ '3 pricing Cards in grid (className="grid gap-4 md:grid-cols-3"). Each Card: CardHeader > CardTitle(text-sm font-medium) for tier name + CardDescription for price (text-2xl font-bold) + period (/month in text-sm text-muted-foreground). CardContent > feature list with checkmark icons (size-4 text-muted-foreground) and text-sm text. CardFooter > Button (w-full). Tiers: Starter ($0/mo, 5 features), Pro ($29/mo, highlighted with bg-primary text-primary-foreground badge, 8 features), Enterprise ($99/mo, 10 features)',
80
+ "FAQ section below with 4 questions/answers, each as a div with question (text-sm font-medium) and answer (text-sm text-muted-foreground)"
81
+ ],
82
+ components: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "CardFooter", "Button", "Badge"]
83
+ },
84
+ about: {
85
+ description: "About page with team and values",
86
+ sections: [
87
+ 'Page header: h1 "About Us" className="text-2xl font-bold tracking-tight" + mission statement p className="text-sm text-muted-foreground"',
88
+ 'Team section: heading "Our Team" (text-sm font-medium uppercase tracking-wide text-muted-foreground) + grid gap-4 md:grid-cols-3 with 3-4 team member Cards. Each Card: avatar placeholder (bg-muted/50 rounded-full size-12), name (text-sm font-medium), role (text-sm text-muted-foreground)',
89
+ "Values section: 3-4 value Cards in grid, each with icon (size-4 text-muted-foreground), title (text-sm font-medium), description (text-sm text-muted-foreground)"
90
+ ],
91
+ components: ["Card", "CardHeader", "CardTitle", "CardContent"]
92
+ },
93
+ contact: {
94
+ description: "Contact page with form",
95
+ sections: [
96
+ 'Page header: h1 "Get in Touch" className="text-2xl font-bold tracking-tight" + p className="text-sm text-muted-foreground"',
97
+ 'Two-column layout: grid gap-6 lg:grid-cols-2. Left: Card with form \u2014 Label+Input for name, email, subject; Label+Textarea for message; Button "Send Message" (w-full). Right: contact info with 3 items (email, phone, address), each with icon (size-4 text-muted-foreground) + label (text-sm font-medium) + value (text-sm text-muted-foreground).',
98
+ 'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
99
+ ],
100
+ components: ["Card", "CardHeader", "CardTitle", "CardContent", "Button", "Input", "Textarea", "Label"]
101
+ },
102
+ landing: {
103
+ description: "Landing page with hero, features, and CTA",
104
+ sections: [
105
+ 'Hero section: centered text, h1 "Build better products, faster" className="text-2xl font-bold tracking-tight md:text-3xl" + p className="text-sm text-muted-foreground" + two Buttons (primary and secondary/outline). Centered with text-center and max-w-2xl mx-auto.',
106
+ "Features section: grid gap-4 md:grid-cols-3 with 3 feature Cards. Each: icon (size-4 text-muted-foreground), title (text-sm font-medium), description (text-sm text-muted-foreground).",
107
+ "CTA section: bg-muted rounded-xl p-6 md:p-10 with centered text and Button."
108
+ ],
109
+ components: ["Card", "CardHeader", "CardTitle", "CardContent", "Button"]
110
+ },
111
+ services: {
112
+ description: "Services page with filterable grid",
113
+ sections: [
114
+ 'Page header: h1 "Services" className="text-2xl font-bold tracking-tight" + p "What we offer" className="text-sm text-muted-foreground"',
115
+ "Filter row: flex gap-2 with Badge-style filter buttons (text-sm). Active filter: bg-primary text-primary-foreground. Inactive: bg-muted text-muted-foreground hover:bg-muted/80.",
116
+ "6 service Cards in grid gap-4 md:grid-cols-2 lg:grid-cols-3. Each Card: CardHeader > CardTitle(text-sm font-medium) + icon(size-4 text-muted-foreground). CardContent > description(text-sm text-muted-foreground). Real service names: Web Design, Mobile Development, SEO Optimization, Cloud Infrastructure, Data Analytics, UI/UX Research."
117
+ ],
118
+ components: ["Card", "CardHeader", "CardTitle", "CardContent", "Badge", "Button"]
119
+ },
120
+ settings: {
121
+ description: "Settings page with grouped form sections",
122
+ sections: [
123
+ 'Page header: h1 "Settings" className="text-2xl font-bold tracking-tight" + p className="text-sm text-muted-foreground"',
124
+ "Sections in flex flex-col gap-6: Profile Card (name, email inputs), Notifications Card (toggle checkboxes), Danger Zone Card (destructive button to delete account). Each Card with CardHeader(CardTitle text-sm font-medium + CardDescription text-sm text-muted-foreground) and CardContent with form fields.",
125
+ 'This page uses "use client" (has useState). Do NOT include export const metadata.'
126
+ ],
127
+ components: [
128
+ "Card",
129
+ "CardHeader",
130
+ "CardTitle",
131
+ "CardDescription",
132
+ "CardContent",
133
+ "Button",
134
+ "Input",
135
+ "Label",
136
+ "Checkbox"
137
+ ]
138
+ },
139
+ blog: {
140
+ description: "Blog or articles list page",
141
+ sections: [
142
+ 'Page header: h1 + description. Grid of article cards (Card with CardHeader title + date/author, CardContent excerpt, CardFooter "Read more" button). Use grid gap-6 md:grid-cols-2.'
143
+ ],
144
+ components: ["Card", "CardHeader", "CardTitle", "CardContent", "CardFooter", "Button"]
145
+ },
146
+ profile: {
147
+ description: "Profile or account page",
148
+ sections: [
149
+ "Avatar section, name and email. Personal info form (Label + Input). Connected accounts section (optional). Recent activity list. Two-column layout on desktop (md:grid-cols-2)."
150
+ ],
151
+ components: [
152
+ "Card",
153
+ "CardHeader",
154
+ "CardTitle",
155
+ "CardDescription",
156
+ "CardContent",
157
+ "Button",
158
+ "Input",
159
+ "Label",
160
+ "Avatar",
161
+ "AvatarFallback"
162
+ ]
163
+ },
164
+ onboarding: {
165
+ description: "Onboarding or wizard (multi-step form)",
166
+ sections: [
167
+ 'Progress indicator (step N of M). One Card per step with CardHeader (title + description), CardContent (form fields), CardFooter (Back / Next or Finish). Use "use client" and useState for step.'
168
+ ],
169
+ components: [
170
+ "Card",
171
+ "CardHeader",
172
+ "CardTitle",
173
+ "CardDescription",
174
+ "CardContent",
175
+ "CardFooter",
176
+ "Button",
177
+ "Input",
178
+ "Label"
179
+ ]
180
+ },
181
+ gallery: {
182
+ description: "Gallery or portfolio (image grid)",
183
+ sections: [
184
+ 'Page header. Optional category filter tabs (Button variant="outline"). Grid of image cards (aspect-square, image + caption). grid-cols-2 md:grid-cols-3 lg:grid-cols-4.'
185
+ ],
186
+ components: ["Card", "CardContent", "Button"]
187
+ },
188
+ faq: {
189
+ description: "FAQ page with accordion",
190
+ sections: [
191
+ "Page header. Accordion or details/summary for each Q&A. Optional category tabs. Use semantic HTML or Collapsible. Max-w-3xl for content."
192
+ ],
193
+ components: ["Card", "Button"]
194
+ },
195
+ changelog: {
196
+ description: "Changelog or release timeline",
197
+ sections: [
198
+ "Page header. Timeline: each version with version badge, date, list of entries (type: text). Border-left timeline pattern. Badge component for version/date."
199
+ ],
200
+ components: ["Badge"]
201
+ },
202
+ team: {
203
+ description: "Team page with member cards",
204
+ sections: [
205
+ 'Page header: h1 "Our Team" className="text-2xl font-bold tracking-tight" + p className="text-sm text-muted-foreground"',
206
+ 'Grid of team member Cards (className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"). Each card: avatar placeholder (bg-muted rounded-full size-12 with initials), name (text-sm font-semibold), role (text-sm text-muted-foreground).'
207
+ ],
208
+ components: ["Card", "CardContent"]
209
+ },
210
+ tasks: {
211
+ description: "Task list page with status badges and search",
212
+ sections: [
213
+ 'Page header: h1 "Tasks" + description. Search input with Search icon.',
214
+ "Task list: divide-y container. Each row: status badge (colored), task title, priority badge, assignee name. Use flex items-center justify-between.",
215
+ 'This page uses "use client" (has useState for search). Do NOT include export const metadata.'
216
+ ],
217
+ components: ["Input", "Badge"]
218
+ },
219
+ "task-detail": {
220
+ description: "Task detail page with info and activity",
221
+ sections: [
222
+ "Back button linking to /tasks. Page header with task title and description.",
223
+ "Two-column layout (md:grid-cols-2): Left Card with task details (status, priority, assignee, due date). Right Card with activity timeline.",
224
+ 'This page uses "use client" (has useState). Do NOT include export const metadata.'
225
+ ],
226
+ components: ["Card", "CardHeader", "CardTitle", "CardContent", "Button"]
227
+ },
228
+ "reset-password": {
229
+ description: "Reset password page with centered card form",
230
+ sections: [
231
+ 'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
232
+ 'Card with CardHeader: CardTitle "Reset Password" (text-xl), CardDescription.',
233
+ 'CardContent with form: new password Input (type="password"), confirm password Input (type="password"), Button "Reset password" (w-full).',
234
+ 'Footer text: "Remember your password?" with Sign in link.',
235
+ 'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
236
+ ],
237
+ components: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "Button", "Input", "Label"]
238
+ }
239
+ };
240
+ var AUTH_ROUTE_SEGMENTS = /* @__PURE__ */ new Set([
241
+ "login",
242
+ "signin",
243
+ "sign-in",
244
+ "sign-up",
245
+ "signup",
246
+ "register",
247
+ "forgot-password",
248
+ "reset-password"
249
+ ]);
250
+ function isAuthRoute(routeOrName) {
251
+ const normalized = routeOrName.toLowerCase().replace(/^\//, "").trim();
252
+ const segment = normalized.split("/")[0] || "";
253
+ return AUTH_ROUTE_SEGMENTS.has(segment) || AUTH_ROUTE_SEGMENTS.has(normalized);
254
+ }
255
+ function detectPageType(pageName) {
256
+ const normalized = pageName.toLowerCase();
257
+ if (/dashboard|admin|overview/.test(normalized)) return "dashboard";
258
+ if (/login|signin|sign-in/.test(normalized)) return "login";
259
+ if (/regist(?:er|ration)|signup|sign.?up/.test(normalized)) return "register";
260
+ if (/pricing|plans|subscription/.test(normalized)) return "pricing";
261
+ if (/about|company/.test(normalized)) return "about";
262
+ if (/contact|support|help/.test(normalized)) return "contact";
263
+ if (/settings|preferences|account/.test(normalized)) return "settings";
264
+ if (/home|landing|hero/.test(normalized)) return "landing";
265
+ if (/services|услуг|каталог|catalog/.test(normalized)) return "services";
266
+ if (/blog|articles|posts/.test(normalized)) return "blog";
267
+ if (/profile|account/.test(normalized) && !/settings|preferences/.test(normalized)) return "profile";
268
+ if (/onboarding|wizard|setup/.test(normalized)) return "onboarding";
269
+ if (/gallery|portfolio|images/.test(normalized)) return "gallery";
270
+ if (/faq|frequently|questions/.test(normalized)) return "faq";
271
+ if (/changelog|release|versions/.test(normalized)) return "changelog";
272
+ if (/team|members/.test(normalized)) return "team";
273
+ if (/tasks?/.test(normalized) && /detail|\[id\]/.test(normalized)) return "task-detail";
274
+ if (/tasks?/.test(normalized)) return "tasks";
275
+ if (/reset.?password/.test(normalized)) return "reset-password";
276
+ return null;
277
+ }
278
+ function expandPageRequest(pageName, userRequest) {
279
+ const pageType = detectPageType(pageName);
280
+ if (!pageType) {
281
+ return userRequest;
282
+ }
283
+ const template = PAGE_TEMPLATES[pageType];
284
+ const hasDetails = userRequest.split(/\s+/).length > 5;
285
+ if (hasDetails) {
286
+ return userRequest;
287
+ }
288
+ return `Add ${pageName} page with:
289
+ ${template.sections.map((s, i) => `${i + 1}. ${s}`).join("\n")}
290
+
291
+ Use these components if available: ${template.components.join(", ")}
292
+ Make it responsive and visually appealing.
293
+ `;
294
+ }
295
+
296
+ // src/commands/chat/code-generator.ts
297
+ import { integrateSharedLayoutIntoRootLayout, generateSharedComponent } from "@getcoherent/core";
298
+
299
+ // src/utils/auth-route-group.ts
300
+ import { join } from "path";
301
+ import { readFile, writeFile } from "fs/promises";
302
+ import { existsSync } from "fs";
303
+ var AUTH_LAYOUT = `export default function AuthLayout({
304
+ children,
305
+ }: {
306
+ children: React.ReactNode
307
+ }) {
308
+ return (
309
+ <div className="min-h-svh bg-muted flex items-center justify-center p-4">
310
+ {children}
311
+ </div>
312
+ )
313
+ }
314
+ `;
315
+ var SHOW_WHEN_NOT_AUTH = `'use client'
316
+
317
+ import { usePathname } from 'next/navigation'
318
+
319
+ const HIDDEN_PATHS = ['/login', '/signin', '/sign-up', '/signup', '/register', '/forgot-password', '/reset-password', '/design-system']
320
+
321
+ export default function ShowWhenNotAuthRoute({
322
+ children,
323
+ }: {
324
+ children: React.ReactNode
325
+ }) {
326
+ const pathname = usePathname()
327
+ if (pathname && HIDDEN_PATHS.some((p) => pathname === p || pathname.startsWith(p + '/'))) {
328
+ return null
329
+ }
330
+ return <>{children}</>
331
+ }
332
+ `;
333
+ async function ensureAuthRouteGroup(projectRoot) {
334
+ const authLayoutPath = join(projectRoot, "app", "(auth)", "layout.tsx");
335
+ const guardPath = join(projectRoot, "app", "ShowWhenNotAuthRoute.tsx");
336
+ const rootLayoutPath = join(projectRoot, "app", "layout.tsx");
337
+ if (!existsSync(authLayoutPath)) {
338
+ const { mkdir: mkdir4 } = await import("fs/promises");
339
+ await mkdir4(join(projectRoot, "app", "(auth)"), { recursive: true });
340
+ await writeFile(authLayoutPath, AUTH_LAYOUT, "utf-8");
341
+ }
342
+ if (!existsSync(guardPath)) {
343
+ await writeFile(guardPath, SHOW_WHEN_NOT_AUTH, "utf-8");
344
+ }
345
+ let layoutContent;
346
+ try {
347
+ layoutContent = await readFile(rootLayoutPath, "utf-8");
348
+ } catch {
349
+ return;
350
+ }
351
+ if (layoutContent.includes("ShowWhenNotAuthRoute")) return;
352
+ if (!layoutContent.includes("from './ShowWhenNotAuthRoute'") && !layoutContent.includes('from "./ShowWhenNotAuthRoute"')) {
353
+ const lines = layoutContent.split("\n");
354
+ let lastImportLineIdx = -1;
355
+ for (let i = 0; i < lines.length; i++) {
356
+ if (/^\s*import\s/.test(lines[i])) lastImportLineIdx = i;
357
+ }
358
+ if (lastImportLineIdx >= 0) {
359
+ lines.splice(lastImportLineIdx + 1, 0, "import ShowWhenNotAuthRoute from './ShowWhenNotAuthRoute'");
360
+ } else {
361
+ lines.unshift("import ShowWhenNotAuthRoute from './ShowWhenNotAuthRoute'");
362
+ }
363
+ layoutContent = lines.join("\n");
364
+ }
365
+ const wrapComponent = (tag) => {
366
+ if (!layoutContent.includes(`<${tag}`)) return;
367
+ const regex = new RegExp(`(\\s*)(<${tag}[^>]*\\/>)`, "g");
368
+ layoutContent = layoutContent.replace(regex, "$1<ShowWhenNotAuthRoute>\n$1 $2\n$1</ShowWhenNotAuthRoute>");
369
+ };
370
+ wrapComponent("Header");
371
+ wrapComponent("Footer");
372
+ await writeFile(rootLayoutPath, layoutContent, "utf-8");
373
+ }
374
+
375
+ // src/commands/chat/code-generator.ts
376
+ import chalk3 from "chalk";
377
+
378
+ // src/utils/files.ts
379
+ import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir, rename, unlink, copyFile, access } from "fs/promises";
380
+ import { dirname, join as join2 } from "path";
381
+ import { existsSync as existsSync2, writeFileSync, unlinkSync, readFileSync } from "fs";
382
+ import { randomBytes } from "crypto";
383
+ async function readFile2(path2) {
384
+ try {
385
+ return await fsReadFile(path2, "utf-8");
386
+ } catch (error) {
387
+ if (error instanceof Error) {
388
+ throw new Error(`Failed to read file ${path2}: ${error.message}`);
389
+ }
390
+ throw error;
391
+ }
392
+ }
393
+ async function writeFile2(path2, content) {
394
+ try {
395
+ const dir = dirname(path2);
396
+ if (!existsSync2(dir)) {
397
+ await mkdir(dir, { recursive: true });
398
+ }
399
+ const tmpPath = `${path2}.${randomBytes(4).toString("hex")}.tmp`;
400
+ await fsWriteFile(tmpPath, content, "utf-8");
401
+ await rename(tmpPath, path2);
402
+ } catch (error) {
403
+ if (error instanceof Error) {
404
+ throw new Error(`Failed to write file ${path2}: ${error.message}`);
405
+ }
406
+ throw error;
407
+ }
408
+ }
409
+ async function fileExistsAsync(path2) {
410
+ try {
411
+ await access(path2);
412
+ return true;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+ var LOCK_FILENAME = ".coherent.lock";
418
+ var LOCK_STALE_MS = 5 * 60 * 1e3;
419
+ async function acquireProjectLock(projectRoot) {
420
+ const lockPath = join2(projectRoot, LOCK_FILENAME);
421
+ if (existsSync2(lockPath)) {
422
+ try {
423
+ const raw = readFileSync(lockPath, "utf-8");
424
+ const data = JSON.parse(raw);
425
+ const age = Date.now() - data.ts;
426
+ if (age < LOCK_STALE_MS) {
427
+ try {
428
+ process.kill(data.pid, 0);
429
+ throw new Error(
430
+ `Another coherent process (PID ${data.pid}) is running. Wait for it to finish or remove ${LOCK_FILENAME}.`
431
+ );
432
+ } catch (e) {
433
+ if (e.code !== "ESRCH") throw e;
434
+ }
435
+ }
436
+ unlinkSync(lockPath);
437
+ } catch (e) {
438
+ if (e instanceof SyntaxError) unlinkSync(lockPath);
439
+ else if (e instanceof Error && e.message.includes("Another coherent")) throw e;
440
+ }
441
+ }
442
+ const lockData = JSON.stringify({ pid: process.pid, ts: Date.now() });
443
+ writeFileSync(lockPath, lockData, "utf-8");
444
+ const release = () => {
445
+ try {
446
+ unlinkSync(lockPath);
447
+ } catch {
448
+ }
449
+ };
450
+ process.on("exit", release);
451
+ process.on("SIGINT", () => {
452
+ release();
453
+ process.exit(130);
454
+ });
455
+ process.on("SIGTERM", () => {
456
+ release();
457
+ process.exit(143);
458
+ });
459
+ return release;
460
+ }
461
+
462
+ // src/utils/file-hashes.ts
463
+ import { createHash } from "crypto";
464
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
465
+ import { join as join3 } from "path";
466
+ var HASHES_FILE = ".coherent/file-hashes.json";
467
+ async function computeFileHash(filePath) {
468
+ const content = await readFile3(filePath, "utf-8");
469
+ return createHash("sha256").update(content).digest("hex");
470
+ }
471
+ async function loadHashes(projectRoot) {
472
+ try {
473
+ const raw = await readFile3(join3(projectRoot, HASHES_FILE), "utf-8");
474
+ return JSON.parse(raw);
475
+ } catch {
476
+ return {};
477
+ }
478
+ }
479
+ async function saveHashes(projectRoot, hashes) {
480
+ const dir = join3(projectRoot, ".coherent");
481
+ await mkdir2(dir, { recursive: true });
482
+ await writeFile3(join3(projectRoot, HASHES_FILE), JSON.stringify(hashes, null, 2) + "\n");
483
+ }
484
+ async function isManuallyEdited(filePath, storedHash) {
485
+ try {
486
+ const currentHash = await computeFileHash(filePath);
487
+ return currentHash !== storedHash;
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+
493
+ // src/providers/shadcn-provider.ts
494
+ import { buildCssVariables } from "@getcoherent/core";
495
+ import { existsSync as fsExistsSync } from "fs";
496
+
497
+ // src/utils/shadcn-installer.ts
498
+ var SHADCN_COMPONENTS = {
499
+ button: {
500
+ id: "button",
501
+ name: "Button",
502
+ category: "form",
503
+ source: "shadcn",
504
+ shadcnComponent: "button",
505
+ baseClassName: "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:opacity-50 disabled:pointer-events-none",
506
+ variants: [
507
+ { name: "default", className: "bg-primary text-primary-foreground shadow-sm hover:opacity-90 active:opacity-80" },
508
+ {
509
+ name: "secondary",
510
+ className: "bg-secondary text-secondary-foreground shadow-sm hover:opacity-90 active:opacity-80"
511
+ },
512
+ {
513
+ name: "outline",
514
+ className: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground active:bg-accent"
515
+ },
516
+ { name: "ghost", className: "hover:bg-accent hover:text-accent-foreground active:bg-accent" },
517
+ { name: "destructive", className: "bg-error text-white shadow-sm hover:opacity-90 active:opacity-80" },
518
+ { name: "link", className: "text-primary underline-offset-4 hover:underline" }
519
+ ],
520
+ sizes: [
521
+ { name: "sm", className: "h-8 px-3 text-xs" },
522
+ { name: "md", className: "h-10 px-4 text-sm" },
523
+ { name: "lg", className: "h-11 px-8 text-base" }
524
+ ],
525
+ usedInPages: [],
526
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
527
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
528
+ },
529
+ input: {
530
+ id: "input",
531
+ name: "Input",
532
+ category: "form",
533
+ source: "shadcn",
534
+ shadcnComponent: "input",
535
+ baseClassName: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
536
+ variants: [
537
+ { name: "default", className: "" },
538
+ { name: "error", className: "border-error focus-visible:ring-error" }
539
+ ],
540
+ sizes: [
541
+ { name: "sm", className: "h-8 text-xs" },
542
+ { name: "md", className: "h-10 text-sm" },
543
+ { name: "lg", className: "h-12 text-base" }
544
+ ],
545
+ usedInPages: [],
546
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
547
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
548
+ },
549
+ textarea: {
550
+ id: "textarea",
551
+ name: "Textarea",
552
+ category: "form",
553
+ source: "shadcn",
554
+ shadcnComponent: "textarea",
555
+ baseClassName: "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
556
+ variants: [
557
+ { name: "default", className: "" },
558
+ { name: "error", className: "border-error focus-visible:ring-error" }
559
+ ],
560
+ sizes: [
561
+ { name: "sm", className: "min-h-[60px] text-xs" },
562
+ { name: "md", className: "min-h-[80px] text-sm" },
563
+ { name: "lg", className: "min-h-[120px] text-base" }
564
+ ],
565
+ usedInPages: [],
566
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
567
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
568
+ },
569
+ checkbox: {
570
+ id: "checkbox",
571
+ name: "Checkbox",
572
+ category: "form",
573
+ source: "shadcn",
574
+ shadcnComponent: "checkbox",
575
+ baseClassName: "peer h-4 w-4 shrink-0 rounded-sm border border-primary",
576
+ variants: [{ name: "default", className: "" }],
577
+ sizes: [
578
+ { name: "sm", className: "h-3 w-3" },
579
+ { name: "md", className: "h-4 w-4" },
580
+ { name: "lg", className: "h-5 w-5" }
581
+ ],
582
+ usedInPages: [],
583
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
584
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
585
+ },
586
+ select: {
587
+ id: "select",
588
+ name: "Select",
589
+ category: "form",
590
+ source: "shadcn",
591
+ shadcnComponent: "select",
592
+ baseClassName: "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2",
593
+ variants: [{ name: "default", className: "" }],
594
+ sizes: [
595
+ { name: "sm", className: "h-8 text-xs" },
596
+ { name: "md", className: "h-10 text-sm" },
597
+ { name: "lg", className: "h-12 text-base" }
598
+ ],
599
+ usedInPages: [],
600
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
601
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
602
+ },
603
+ dialog: {
604
+ id: "dialog",
605
+ name: "Dialog",
606
+ category: "overlay",
607
+ source: "shadcn",
608
+ shadcnComponent: "dialog",
609
+ baseClassName: "fixed z-50 grid w-full gap-4 border bg-background p-6 shadow-lg",
610
+ variants: [{ name: "default", className: "" }],
611
+ sizes: [
612
+ { name: "sm", className: "max-w-sm" },
613
+ { name: "md", className: "max-w-lg" },
614
+ { name: "lg", className: "max-w-2xl" }
615
+ ],
616
+ usedInPages: [],
617
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
618
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
619
+ },
620
+ badge: {
621
+ id: "badge",
622
+ name: "Badge",
623
+ category: "data-display",
624
+ source: "shadcn",
625
+ shadcnComponent: "badge",
626
+ baseClassName: "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold",
627
+ variants: [
628
+ { name: "default", className: "border-transparent bg-primary text-primary-foreground" },
629
+ { name: "secondary", className: "border-transparent bg-secondary text-secondary-foreground" },
630
+ { name: "success", className: "border-transparent bg-success text-white" },
631
+ { name: "error", className: "border-transparent bg-error text-white" },
632
+ { name: "outline", className: "text-foreground" }
633
+ ],
634
+ sizes: [
635
+ { name: "sm", className: "text-xs px-2 py-0.5" },
636
+ { name: "md", className: "text-sm px-2.5 py-0.5" },
637
+ { name: "lg", className: "text-base px-3 py-1" }
638
+ ],
639
+ usedInPages: [],
640
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
641
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
642
+ },
643
+ card: {
644
+ id: "card",
645
+ name: "Card",
646
+ category: "layout",
647
+ source: "shadcn",
648
+ shadcnComponent: "card",
649
+ baseClassName: "rounded-lg border bg-background text-foreground shadow-sm",
650
+ variants: [
651
+ { name: "default", className: "" },
652
+ { name: "outlined", className: "border-2" },
653
+ { name: "elevated", className: "shadow-lg" },
654
+ { name: "interactive", className: "hover:shadow-md transition-shadow cursor-pointer" }
655
+ ],
656
+ sizes: [
657
+ { name: "sm", className: "p-3" },
658
+ { name: "md", className: "p-6" },
659
+ { name: "lg", className: "p-8" }
660
+ ],
661
+ usedInPages: [],
662
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
663
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
664
+ },
665
+ // Form Components
666
+ label: {
667
+ id: "label",
668
+ name: "Label",
669
+ category: "form",
670
+ source: "shadcn",
671
+ shadcnComponent: "label",
672
+ baseClassName: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
673
+ variants: [{ name: "default", className: "" }],
674
+ sizes: [{ name: "md", className: "" }],
675
+ usedInPages: [],
676
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
677
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
678
+ },
679
+ "radio-group": {
680
+ id: "radio-group",
681
+ name: "RadioGroup",
682
+ category: "form",
683
+ source: "shadcn",
684
+ shadcnComponent: "radio-group",
685
+ baseClassName: "grid gap-2",
686
+ variants: [{ name: "default", className: "" }],
687
+ sizes: [
688
+ { name: "sm", className: "gap-1" },
689
+ { name: "md", className: "gap-2" },
690
+ { name: "lg", className: "gap-3" }
691
+ ],
692
+ usedInPages: [],
693
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
694
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
695
+ },
696
+ switch: {
697
+ id: "switch",
698
+ name: "Switch",
699
+ category: "form",
700
+ source: "shadcn",
701
+ shadcnComponent: "switch",
702
+ baseClassName: "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors",
703
+ variants: [{ name: "default", className: "" }],
704
+ sizes: [
705
+ { name: "sm", className: "h-5 w-9" },
706
+ { name: "md", className: "h-6 w-11" },
707
+ { name: "lg", className: "h-7 w-14" }
708
+ ],
709
+ usedInPages: [],
710
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
711
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
712
+ },
713
+ slider: {
714
+ id: "slider",
715
+ name: "Slider",
716
+ category: "form",
717
+ source: "shadcn",
718
+ shadcnComponent: "slider",
719
+ baseClassName: "relative flex w-full touch-none select-none items-center",
720
+ variants: [{ name: "default", className: "" }],
721
+ sizes: [
722
+ { name: "sm", className: "h-1" },
723
+ { name: "md", className: "h-2" },
724
+ { name: "lg", className: "h-3" }
725
+ ],
726
+ usedInPages: [],
727
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
728
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
729
+ },
730
+ // Layout Components
731
+ separator: {
732
+ id: "separator",
733
+ name: "Separator",
734
+ category: "layout",
735
+ source: "shadcn",
736
+ shadcnComponent: "separator",
737
+ baseClassName: "shrink-0 bg-border",
738
+ variants: [
739
+ { name: "horizontal", className: "h-[1px] w-full" },
740
+ { name: "vertical", className: "h-full w-[1px]" }
741
+ ],
742
+ sizes: [{ name: "md", className: "" }],
743
+ usedInPages: [],
744
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
745
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
746
+ },
747
+ accordion: {
748
+ id: "accordion",
749
+ name: "Accordion",
750
+ category: "layout",
751
+ source: "shadcn",
752
+ shadcnComponent: "accordion",
753
+ baseClassName: "w-full",
754
+ variants: [
755
+ { name: "default", className: "" },
756
+ { name: "bordered", className: "border rounded-md" }
757
+ ],
758
+ sizes: [{ name: "md", className: "" }],
759
+ usedInPages: [],
760
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
761
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
762
+ },
763
+ tabs: {
764
+ id: "tabs",
765
+ name: "Tabs",
766
+ category: "layout",
767
+ source: "shadcn",
768
+ shadcnComponent: "tabs",
769
+ baseClassName: "w-full",
770
+ variants: [
771
+ { name: "default", className: "" },
772
+ { name: "bordered", className: "border rounded-md p-4" }
773
+ ],
774
+ sizes: [{ name: "md", className: "" }],
775
+ usedInPages: [],
776
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
777
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
778
+ },
779
+ collapsible: {
780
+ id: "collapsible",
781
+ name: "Collapsible",
782
+ category: "layout",
783
+ source: "shadcn",
784
+ shadcnComponent: "collapsible",
785
+ baseClassName: "w-full space-y-2",
786
+ variants: [{ name: "default", className: "" }],
787
+ sizes: [{ name: "md", className: "" }],
788
+ usedInPages: [],
789
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
790
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
791
+ },
792
+ // Navigation Components
793
+ "navigation-menu": {
794
+ id: "navigation-menu",
795
+ name: "NavigationMenu",
796
+ category: "navigation",
797
+ source: "shadcn",
798
+ shadcnComponent: "navigation-menu",
799
+ baseClassName: "relative z-10 flex max-w-max flex-1 items-center justify-center",
800
+ variants: [{ name: "default", className: "" }],
801
+ sizes: [{ name: "md", className: "" }],
802
+ usedInPages: [],
803
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
804
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
805
+ },
806
+ menubar: {
807
+ id: "menubar",
808
+ name: "Menubar",
809
+ category: "navigation",
810
+ source: "shadcn",
811
+ shadcnComponent: "menubar",
812
+ baseClassName: "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
813
+ variants: [{ name: "default", className: "" }],
814
+ sizes: [
815
+ { name: "sm", className: "h-8" },
816
+ { name: "md", className: "h-10" },
817
+ { name: "lg", className: "h-12" }
818
+ ],
819
+ usedInPages: [],
820
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
821
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
822
+ },
823
+ breadcrumb: {
824
+ id: "breadcrumb",
825
+ name: "Breadcrumb",
826
+ category: "navigation",
827
+ source: "shadcn",
828
+ shadcnComponent: "breadcrumb",
829
+ baseClassName: "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground",
830
+ variants: [{ name: "default", className: "" }],
831
+ sizes: [{ name: "md", className: "" }],
832
+ usedInPages: [],
833
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
834
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
835
+ },
836
+ pagination: {
837
+ id: "pagination",
838
+ name: "Pagination",
839
+ category: "navigation",
840
+ source: "shadcn",
841
+ shadcnComponent: "pagination",
842
+ baseClassName: "mx-auto flex w-full justify-center",
843
+ variants: [{ name: "default", className: "" }],
844
+ sizes: [
845
+ { name: "sm", className: "text-xs" },
846
+ { name: "md", className: "text-sm" },
847
+ { name: "lg", className: "text-base" }
848
+ ],
849
+ usedInPages: [],
850
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
851
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
852
+ },
853
+ // Data Display
854
+ table: {
855
+ id: "table",
856
+ name: "Table",
857
+ category: "data-display",
858
+ source: "shadcn",
859
+ shadcnComponent: "table",
860
+ baseClassName: "w-full caption-bottom text-sm",
861
+ variants: [
862
+ { name: "default", className: "" },
863
+ { name: "striped", className: "[&_tr:nth-child(even)]:bg-muted/50" }
864
+ ],
865
+ sizes: [
866
+ { name: "sm", className: "text-xs" },
867
+ { name: "md", className: "text-sm" },
868
+ { name: "lg", className: "text-base" }
869
+ ],
870
+ usedInPages: [],
871
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
872
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
873
+ },
874
+ avatar: {
875
+ id: "avatar",
876
+ name: "Avatar",
877
+ category: "data-display",
878
+ source: "shadcn",
879
+ shadcnComponent: "avatar",
880
+ baseClassName: "relative flex shrink-0 overflow-hidden rounded-full",
881
+ variants: [{ name: "default", className: "" }],
882
+ sizes: [
883
+ { name: "sm", className: "h-8 w-8" },
884
+ { name: "md", className: "h-10 w-10" },
885
+ { name: "lg", className: "h-12 w-12" },
886
+ { name: "xl", className: "h-16 w-16" }
887
+ ],
888
+ usedInPages: [],
889
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
890
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
891
+ },
892
+ progress: {
893
+ id: "progress",
894
+ name: "Progress",
895
+ category: "data-display",
896
+ source: "shadcn",
897
+ shadcnComponent: "progress",
898
+ baseClassName: "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
899
+ variants: [{ name: "default", className: "" }],
900
+ sizes: [
901
+ { name: "sm", className: "h-2" },
902
+ { name: "md", className: "h-4" },
903
+ { name: "lg", className: "h-6" }
904
+ ],
905
+ usedInPages: [],
906
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
907
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
908
+ },
909
+ skeleton: {
910
+ id: "skeleton",
911
+ name: "Skeleton",
912
+ category: "data-display",
913
+ source: "shadcn",
914
+ shadcnComponent: "skeleton",
915
+ baseClassName: "animate-pulse rounded-md bg-muted",
916
+ variants: [{ name: "default", className: "" }],
917
+ sizes: [{ name: "md", className: "" }],
918
+ usedInPages: [],
919
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
920
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
921
+ },
922
+ // Overlay Components
923
+ "alert-dialog": {
924
+ id: "alert-dialog",
925
+ name: "AlertDialog",
926
+ category: "overlay",
927
+ source: "shadcn",
928
+ shadcnComponent: "alert-dialog",
929
+ baseClassName: "fixed z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg",
930
+ variants: [{ name: "default", className: "" }],
931
+ sizes: [
932
+ { name: "sm", className: "max-w-sm" },
933
+ { name: "md", className: "max-w-lg" },
934
+ { name: "lg", className: "max-w-2xl" }
935
+ ],
936
+ usedInPages: [],
937
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
938
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
939
+ },
940
+ sheet: {
941
+ id: "sheet",
942
+ name: "Sheet",
943
+ category: "overlay",
944
+ source: "shadcn",
945
+ shadcnComponent: "sheet",
946
+ baseClassName: "fixed z-50 gap-4 bg-background p-6 shadow-lg transition",
947
+ variants: [
948
+ { name: "top", className: "inset-x-0 top-0 border-b" },
949
+ { name: "bottom", className: "inset-x-0 bottom-0 border-t" },
950
+ { name: "left", className: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm" },
951
+ { name: "right", className: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm" }
952
+ ],
953
+ sizes: [{ name: "md", className: "" }],
954
+ usedInPages: [],
955
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
956
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
957
+ },
958
+ popover: {
959
+ id: "popover",
960
+ name: "Popover",
961
+ category: "overlay",
962
+ source: "shadcn",
963
+ shadcnComponent: "popover",
964
+ baseClassName: "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
965
+ variants: [{ name: "default", className: "" }],
966
+ sizes: [
967
+ { name: "sm", className: "w-56" },
968
+ { name: "md", className: "w-72" },
969
+ { name: "lg", className: "w-96" }
970
+ ],
971
+ usedInPages: [],
972
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
973
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
974
+ },
975
+ tooltip: {
976
+ id: "tooltip",
977
+ name: "Tooltip",
978
+ category: "overlay",
979
+ source: "shadcn",
980
+ shadcnComponent: "tooltip",
981
+ baseClassName: "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
982
+ variants: [{ name: "default", className: "" }],
983
+ sizes: [{ name: "md", className: "" }],
984
+ usedInPages: [],
985
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
986
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
987
+ },
988
+ "dropdown-menu": {
989
+ id: "dropdown-menu",
990
+ name: "DropdownMenu",
991
+ category: "overlay",
992
+ source: "shadcn",
993
+ shadcnComponent: "dropdown-menu",
994
+ baseClassName: "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
995
+ variants: [{ name: "default", className: "" }],
996
+ sizes: [
997
+ { name: "sm", className: "min-w-[6rem]" },
998
+ { name: "md", className: "min-w-[8rem]" },
999
+ { name: "lg", className: "min-w-[12rem]" }
1000
+ ],
1001
+ usedInPages: [],
1002
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1003
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1004
+ },
1005
+ "context-menu": {
1006
+ id: "context-menu",
1007
+ name: "ContextMenu",
1008
+ category: "overlay",
1009
+ source: "shadcn",
1010
+ shadcnComponent: "context-menu",
1011
+ baseClassName: "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
1012
+ variants: [{ name: "default", className: "" }],
1013
+ sizes: [{ name: "md", className: "" }],
1014
+ usedInPages: [],
1015
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1016
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1017
+ },
1018
+ "hover-card": {
1019
+ id: "hover-card",
1020
+ name: "HoverCard",
1021
+ category: "overlay",
1022
+ source: "shadcn",
1023
+ shadcnComponent: "hover-card",
1024
+ baseClassName: "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
1025
+ variants: [{ name: "default", className: "" }],
1026
+ sizes: [
1027
+ { name: "sm", className: "w-48" },
1028
+ { name: "md", className: "w-64" },
1029
+ { name: "lg", className: "w-80" }
1030
+ ],
1031
+ usedInPages: [],
1032
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1033
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1034
+ },
1035
+ // Feedback Components
1036
+ alert: {
1037
+ id: "alert",
1038
+ name: "Alert",
1039
+ category: "feedback",
1040
+ source: "shadcn",
1041
+ shadcnComponent: "alert",
1042
+ baseClassName: "relative w-full rounded-lg border p-4",
1043
+ variants: [
1044
+ { name: "default", className: "bg-background text-foreground" },
1045
+ { name: "destructive", className: "border-destructive/50 text-destructive dark:border-destructive" }
1046
+ ],
1047
+ sizes: [{ name: "md", className: "" }],
1048
+ usedInPages: [],
1049
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1050
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1051
+ },
1052
+ toast: {
1053
+ id: "toast",
1054
+ name: "Toast",
1055
+ category: "feedback",
1056
+ source: "shadcn",
1057
+ shadcnComponent: "toast",
1058
+ baseClassName: "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all",
1059
+ variants: [
1060
+ { name: "default", className: "" },
1061
+ {
1062
+ name: "destructive",
1063
+ className: "destructive group border-destructive bg-destructive text-destructive-foreground"
1064
+ }
1065
+ ],
1066
+ sizes: [{ name: "md", className: "" }],
1067
+ usedInPages: [],
1068
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1069
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1070
+ },
1071
+ // Advanced Components
1072
+ calendar: {
1073
+ id: "calendar",
1074
+ name: "Calendar",
1075
+ category: "form",
1076
+ source: "shadcn",
1077
+ shadcnComponent: "calendar",
1078
+ baseClassName: "p-3",
1079
+ variants: [{ name: "default", className: "" }],
1080
+ sizes: [{ name: "md", className: "" }],
1081
+ usedInPages: [],
1082
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1083
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1084
+ },
1085
+ command: {
1086
+ id: "command",
1087
+ name: "Command",
1088
+ category: "form",
1089
+ source: "shadcn",
1090
+ shadcnComponent: "command",
1091
+ baseClassName: "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
1092
+ variants: [{ name: "default", className: "" }],
1093
+ sizes: [{ name: "md", className: "" }],
1094
+ usedInPages: [],
1095
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1096
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1097
+ },
1098
+ combobox: {
1099
+ id: "combobox",
1100
+ name: "Combobox",
1101
+ category: "form",
1102
+ source: "shadcn",
1103
+ shadcnComponent: "combobox",
1104
+ baseClassName: "w-full",
1105
+ variants: [{ name: "default", className: "" }],
1106
+ sizes: [{ name: "md", className: "" }],
1107
+ usedInPages: [],
1108
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1109
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1110
+ },
1111
+ "aspect-ratio": {
1112
+ id: "aspect-ratio",
1113
+ name: "AspectRatio",
1114
+ category: "layout",
1115
+ source: "shadcn",
1116
+ shadcnComponent: "aspect-ratio",
1117
+ baseClassName: "relative w-full",
1118
+ variants: [
1119
+ { name: "16/9", className: "aspect-video" },
1120
+ { name: "4/3", className: "aspect-4/3" },
1121
+ { name: "1/1", className: "aspect-square" }
1122
+ ],
1123
+ sizes: [{ name: "md", className: "" }],
1124
+ usedInPages: [],
1125
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1126
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1127
+ },
1128
+ "scroll-area": {
1129
+ id: "scroll-area",
1130
+ name: "ScrollArea",
1131
+ category: "layout",
1132
+ source: "shadcn",
1133
+ shadcnComponent: "scroll-area",
1134
+ baseClassName: "relative overflow-hidden",
1135
+ variants: [{ name: "default", className: "" }],
1136
+ sizes: [{ name: "md", className: "" }],
1137
+ usedInPages: [],
1138
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1139
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1140
+ },
1141
+ toggle: {
1142
+ id: "toggle",
1143
+ name: "Toggle",
1144
+ category: "form",
1145
+ source: "shadcn",
1146
+ shadcnComponent: "toggle",
1147
+ baseClassName: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2",
1148
+ variants: [
1149
+ { name: "default", className: "" },
1150
+ { name: "outline", className: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground" }
1151
+ ],
1152
+ sizes: [
1153
+ { name: "sm", className: "h-9 px-2.5" },
1154
+ { name: "md", className: "h-10 px-3" },
1155
+ { name: "lg", className: "h-11 px-5" }
1156
+ ],
1157
+ usedInPages: [],
1158
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1159
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1160
+ },
1161
+ "toggle-group": {
1162
+ id: "toggle-group",
1163
+ name: "ToggleGroup",
1164
+ category: "form",
1165
+ source: "shadcn",
1166
+ shadcnComponent: "toggle-group",
1167
+ baseClassName: "flex items-center justify-center gap-1",
1168
+ variants: [{ name: "default", className: "" }],
1169
+ sizes: [{ name: "md", className: "" }],
1170
+ usedInPages: [],
1171
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1172
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1173
+ },
1174
+ carousel: {
1175
+ id: "carousel",
1176
+ name: "Carousel",
1177
+ category: "data-display",
1178
+ source: "shadcn",
1179
+ shadcnComponent: "carousel",
1180
+ baseClassName: "relative w-full",
1181
+ variants: [{ name: "default", className: "" }],
1182
+ sizes: [{ name: "md", className: "" }],
1183
+ usedInPages: [],
1184
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1185
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1186
+ },
1187
+ drawer: {
1188
+ id: "drawer",
1189
+ name: "Drawer",
1190
+ category: "overlay",
1191
+ source: "shadcn",
1192
+ shadcnComponent: "drawer",
1193
+ baseClassName: "fixed z-50 gap-4 bg-background p-6 shadow-lg",
1194
+ variants: [
1195
+ { name: "bottom", className: "inset-x-0 bottom-0 border-t" },
1196
+ { name: "left", className: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm" },
1197
+ { name: "right", className: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm" }
1198
+ ],
1199
+ sizes: [{ name: "md", className: "" }],
1200
+ usedInPages: [],
1201
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1202
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1203
+ },
1204
+ resizable: {
1205
+ id: "resizable",
1206
+ name: "Resizable",
1207
+ category: "layout",
1208
+ source: "shadcn",
1209
+ shadcnComponent: "resizable",
1210
+ baseClassName: "flex h-full w-full",
1211
+ variants: [
1212
+ { name: "horizontal", className: "flex-row" },
1213
+ { name: "vertical", className: "flex-col" }
1214
+ ],
1215
+ sizes: [{ name: "md", className: "" }],
1216
+ usedInPages: [],
1217
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1218
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1219
+ },
1220
+ sonner: {
1221
+ id: "sonner",
1222
+ name: "Sonner",
1223
+ category: "feedback",
1224
+ source: "shadcn",
1225
+ shadcnComponent: "sonner",
1226
+ baseClassName: "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all",
1227
+ variants: [{ name: "default", className: "" }],
1228
+ sizes: [{ name: "md", className: "" }],
1229
+ usedInPages: [],
1230
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1231
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1232
+ }
1233
+ };
1234
+ function getShadcnComponent(name) {
1235
+ const normalized = name.toLowerCase();
1236
+ return SHADCN_COMPONENTS[normalized] ?? null;
1237
+ }
1238
+ var _shadcnComponentsCache = null;
1239
+ function listShadcnComponents() {
1240
+ if (!_shadcnComponentsCache) {
1241
+ _shadcnComponentsCache = Object.keys(SHADCN_COMPONENTS);
1242
+ }
1243
+ return _shadcnComponentsCache;
1244
+ }
1245
+
1246
+ // src/providers/shadcn-provider.ts
1247
+ import { exec as cpExec } from "child_process";
1248
+ import * as path from "path";
1249
+ function toKebab(id) {
1250
+ return id.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1251
+ }
1252
+ var defaultDeps = { exec: cpExec, existsSync: fsExistsSync };
1253
+ var COMPONENT_REGISTRY = [
1254
+ // Form
1255
+ { id: "button", name: "Button", category: "form", managed: true },
1256
+ { id: "input", name: "Input", category: "form", managed: true },
1257
+ { id: "textarea", name: "Textarea", category: "form", managed: true },
1258
+ { id: "checkbox", name: "Checkbox", category: "form", managed: true },
1259
+ { id: "radio-group", name: "RadioGroup", category: "form", managed: true },
1260
+ { id: "select", name: "Select", category: "form", managed: true },
1261
+ { id: "switch", name: "Switch", category: "form", managed: true },
1262
+ { id: "slider", name: "Slider", category: "form", managed: true },
1263
+ { id: "label", name: "Label", category: "form", managed: true },
1264
+ { id: "form", name: "Form", category: "form", managed: true },
1265
+ { id: "calendar", name: "Calendar", category: "form", managed: true },
1266
+ { id: "date-picker", name: "DatePicker", category: "form", managed: true },
1267
+ { id: "command", name: "Command", category: "form", managed: true },
1268
+ { id: "combobox", name: "Combobox", category: "form", managed: true },
1269
+ { id: "toggle", name: "Toggle", category: "form", managed: true },
1270
+ { id: "toggle-group", name: "ToggleGroup", category: "form", managed: true },
1271
+ { id: "input-otp", name: "InputOTP", category: "form", managed: true },
1272
+ // Layout
1273
+ { id: "card", name: "Card", category: "layout", managed: true },
1274
+ { id: "separator", name: "Separator", category: "layout", managed: true },
1275
+ { id: "accordion", name: "Accordion", category: "layout", managed: true },
1276
+ { id: "tabs", name: "Tabs", category: "layout", managed: true },
1277
+ { id: "collapsible", name: "Collapsible", category: "layout", managed: true },
1278
+ { id: "aspect-ratio", name: "AspectRatio", category: "layout", managed: true },
1279
+ { id: "scroll-area", name: "ScrollArea", category: "layout", managed: true },
1280
+ { id: "resizable", name: "Resizable", category: "layout", managed: true },
1281
+ // Navigation
1282
+ { id: "navigation-menu", name: "NavigationMenu", category: "navigation", managed: true },
1283
+ { id: "menubar", name: "Menubar", category: "navigation", managed: true },
1284
+ { id: "breadcrumb", name: "Breadcrumb", category: "navigation", managed: true },
1285
+ { id: "pagination", name: "Pagination", category: "navigation", managed: true },
1286
+ { id: "sidebar", name: "Sidebar", category: "navigation", managed: true },
1287
+ // Data Display
1288
+ { id: "table", name: "Table", category: "data-display", managed: true },
1289
+ { id: "avatar", name: "Avatar", category: "data-display", managed: true },
1290
+ { id: "badge", name: "Badge", category: "data-display", managed: true },
1291
+ { id: "progress", name: "Progress", category: "data-display", managed: true },
1292
+ { id: "skeleton", name: "Skeleton", category: "data-display", managed: true },
1293
+ { id: "carousel", name: "Carousel", category: "data-display", managed: true },
1294
+ { id: "chart", name: "Chart", category: "data-display", managed: true },
1295
+ // Overlay
1296
+ { id: "dialog", name: "Dialog", category: "overlay", managed: true },
1297
+ { id: "alert-dialog", name: "AlertDialog", category: "overlay", managed: true },
1298
+ { id: "sheet", name: "Sheet", category: "overlay", managed: true },
1299
+ { id: "popover", name: "Popover", category: "overlay", managed: true },
1300
+ { id: "tooltip", name: "Tooltip", category: "overlay", managed: true },
1301
+ { id: "dropdown-menu", name: "DropdownMenu", category: "overlay", managed: true },
1302
+ { id: "context-menu", name: "ContextMenu", category: "overlay", managed: true },
1303
+ { id: "hover-card", name: "HoverCard", category: "overlay", managed: true },
1304
+ { id: "drawer", name: "Drawer", category: "overlay", managed: true },
1305
+ // Feedback
1306
+ { id: "alert", name: "Alert", category: "feedback", managed: true },
1307
+ { id: "toast", name: "Toast", category: "feedback", managed: true },
1308
+ { id: "sonner", name: "Sonner", category: "feedback", managed: true },
1309
+ // Typography
1310
+ { id: "typography", name: "Typography", category: "typography", managed: true }
1311
+ ];
1312
+ var COMPONENT_APIS = {
1313
+ sidebar: {
1314
+ name: "Sidebar",
1315
+ subcomponents: [
1316
+ "SidebarProvider",
1317
+ "Sidebar",
1318
+ "SidebarContent",
1319
+ "SidebarHeader",
1320
+ "SidebarFooter",
1321
+ "SidebarGroup",
1322
+ "SidebarGroupLabel",
1323
+ "SidebarGroupContent",
1324
+ "SidebarMenu",
1325
+ "SidebarMenuItem",
1326
+ "SidebarMenuButton",
1327
+ "SidebarMenuSub",
1328
+ "SidebarMenuSubItem",
1329
+ "SidebarMenuSubButton",
1330
+ "SidebarMenuAction",
1331
+ "SidebarMenuBadge",
1332
+ "SidebarSeparator",
1333
+ "SidebarRail",
1334
+ "SidebarTrigger",
1335
+ "SidebarInset"
1336
+ ],
1337
+ importPath: "@/components/ui/sidebar",
1338
+ keyProps: {
1339
+ side: '"left" | "right"',
1340
+ variant: '"sidebar" | "floating" | "inset"',
1341
+ collapsible: '"offcanvas" | "icon" | "none"'
1342
+ },
1343
+ usage: `<SidebarProvider>
1344
+ <Sidebar>
1345
+ <SidebarContent>
1346
+ <SidebarGroup>
1347
+ <SidebarGroupLabel>Menu</SidebarGroupLabel>
1348
+ <SidebarGroupContent>
1349
+ <SidebarMenu>
1350
+ <SidebarMenuItem>
1351
+ <SidebarMenuButton asChild>
1352
+ <a href="/dashboard"><Home />Dashboard</a>
1353
+ </SidebarMenuButton>
1354
+ </SidebarMenuItem>
1355
+ </SidebarMenu>
1356
+ </SidebarGroupContent>
1357
+ </SidebarGroup>
1358
+ </SidebarContent>
1359
+ </Sidebar>
1360
+ <SidebarInset>{children}</SidebarInset>
1361
+ </SidebarProvider>`,
1362
+ antiPatterns: [
1363
+ "NEVER use Button for sidebar navigation \u2014 use SidebarMenuButton",
1364
+ "NEVER build custom sidebar from div/nav \u2014 use the Sidebar component",
1365
+ "NEVER use Sheet for mobile sidebar \u2014 Sidebar handles responsive behavior automatically",
1366
+ "NEVER forget SidebarProvider wrapper \u2014 it manages open/close state"
1367
+ ]
1368
+ },
1369
+ dialog: {
1370
+ name: "Dialog",
1371
+ subcomponents: [
1372
+ "Dialog",
1373
+ "DialogTrigger",
1374
+ "DialogContent",
1375
+ "DialogHeader",
1376
+ "DialogFooter",
1377
+ "DialogTitle",
1378
+ "DialogDescription",
1379
+ "DialogClose"
1380
+ ],
1381
+ importPath: "@/components/ui/dialog",
1382
+ keyProps: {
1383
+ open: "boolean",
1384
+ onOpenChange: "(open: boolean) => void"
1385
+ },
1386
+ usage: `<Dialog>
1387
+ <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
1388
+ <DialogContent>
1389
+ <DialogHeader>
1390
+ <DialogTitle>Title</DialogTitle>
1391
+ <DialogDescription>Description</DialogDescription>
1392
+ </DialogHeader>
1393
+ <DialogFooter><Button>Save</Button></DialogFooter>
1394
+ </DialogContent>
1395
+ </Dialog>`,
1396
+ antiPatterns: [
1397
+ "NEVER omit DialogTitle \u2014 required for accessibility",
1398
+ "NEVER nest interactive elements without asChild on DialogTrigger"
1399
+ ]
1400
+ },
1401
+ sheet: {
1402
+ name: "Sheet",
1403
+ subcomponents: [
1404
+ "Sheet",
1405
+ "SheetTrigger",
1406
+ "SheetContent",
1407
+ "SheetHeader",
1408
+ "SheetFooter",
1409
+ "SheetTitle",
1410
+ "SheetDescription",
1411
+ "SheetClose"
1412
+ ],
1413
+ importPath: "@/components/ui/sheet",
1414
+ keyProps: {
1415
+ side: '"top" | "right" | "bottom" | "left"'
1416
+ },
1417
+ usage: `<Sheet>
1418
+ <SheetTrigger asChild><Button variant="outline">Open</Button></SheetTrigger>
1419
+ <SheetContent side="right">
1420
+ <SheetHeader>
1421
+ <SheetTitle>Title</SheetTitle>
1422
+ <SheetDescription>Description</SheetDescription>
1423
+ </SheetHeader>
1424
+ </SheetContent>
1425
+ </Sheet>`,
1426
+ antiPatterns: [
1427
+ "NEVER omit SheetTitle \u2014 required for accessibility",
1428
+ "NEVER use for sidebar navigation \u2014 use Sidebar component instead"
1429
+ ]
1430
+ },
1431
+ select: {
1432
+ name: "Select",
1433
+ subcomponents: [
1434
+ "Select",
1435
+ "SelectTrigger",
1436
+ "SelectValue",
1437
+ "SelectContent",
1438
+ "SelectGroup",
1439
+ "SelectLabel",
1440
+ "SelectItem",
1441
+ "SelectSeparator"
1442
+ ],
1443
+ importPath: "@/components/ui/select",
1444
+ keyProps: {
1445
+ value: "string",
1446
+ onValueChange: "(value: string) => void",
1447
+ defaultValue: "string"
1448
+ },
1449
+ usage: `<Select>
1450
+ <SelectTrigger><SelectValue placeholder="Select..." /></SelectTrigger>
1451
+ <SelectContent>
1452
+ <SelectItem value="a">Option A</SelectItem>
1453
+ <SelectItem value="b">Option B</SelectItem>
1454
+ </SelectContent>
1455
+ </Select>`,
1456
+ antiPatterns: [
1457
+ "NEVER use native <select> \u2014 always use shadcn Select compound component",
1458
+ "NEVER put text directly in SelectTrigger \u2014 use SelectValue with placeholder"
1459
+ ]
1460
+ },
1461
+ "dropdown-menu": {
1462
+ name: "DropdownMenu",
1463
+ subcomponents: [
1464
+ "DropdownMenu",
1465
+ "DropdownMenuTrigger",
1466
+ "DropdownMenuContent",
1467
+ "DropdownMenuItem",
1468
+ "DropdownMenuCheckboxItem",
1469
+ "DropdownMenuRadioItem",
1470
+ "DropdownMenuLabel",
1471
+ "DropdownMenuSeparator",
1472
+ "DropdownMenuGroup",
1473
+ "DropdownMenuSub",
1474
+ "DropdownMenuSubTrigger",
1475
+ "DropdownMenuSubContent",
1476
+ "DropdownMenuRadioGroup",
1477
+ "DropdownMenuShortcut"
1478
+ ],
1479
+ importPath: "@/components/ui/dropdown-menu",
1480
+ keyProps: {
1481
+ "DropdownMenuItem.variant": '"default" | "destructive"',
1482
+ asChild: "boolean (on trigger)"
1483
+ },
1484
+ usage: `<DropdownMenu>
1485
+ <DropdownMenuTrigger asChild><Button variant="ghost">Menu</Button></DropdownMenuTrigger>
1486
+ <DropdownMenuContent>
1487
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
1488
+ <DropdownMenuSeparator />
1489
+ <DropdownMenuItem>Edit</DropdownMenuItem>
1490
+ <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
1491
+ </DropdownMenuContent>
1492
+ </DropdownMenu>`,
1493
+ antiPatterns: [
1494
+ "NEVER nest <button> inside DropdownMenuTrigger \u2014 use asChild",
1495
+ 'NEVER use className="text-destructive" on DropdownMenuItem \u2014 use variant="destructive"'
1496
+ ]
1497
+ },
1498
+ "navigation-menu": {
1499
+ name: "NavigationMenu",
1500
+ subcomponents: [
1501
+ "NavigationMenu",
1502
+ "NavigationMenuList",
1503
+ "NavigationMenuItem",
1504
+ "NavigationMenuTrigger",
1505
+ "NavigationMenuContent",
1506
+ "NavigationMenuLink",
1507
+ "NavigationMenuIndicator",
1508
+ "NavigationMenuViewport"
1509
+ ],
1510
+ importPath: "@/components/ui/navigation-menu",
1511
+ keyProps: {},
1512
+ usage: `<NavigationMenu>
1513
+ <NavigationMenuList>
1514
+ <NavigationMenuItem>
1515
+ <NavigationMenuLink asChild>
1516
+ <a href="/about">About</a>
1517
+ </NavigationMenuLink>
1518
+ </NavigationMenuItem>
1519
+ </NavigationMenuList>
1520
+ </NavigationMenu>`,
1521
+ antiPatterns: [
1522
+ "NEVER use for sidebar navigation \u2014 use Sidebar component",
1523
+ "NEVER use plain <nav> with Button links \u2014 use NavigationMenu for top nav"
1524
+ ]
1525
+ },
1526
+ command: {
1527
+ name: "Command",
1528
+ subcomponents: [
1529
+ "Command",
1530
+ "CommandInput",
1531
+ "CommandList",
1532
+ "CommandEmpty",
1533
+ "CommandGroup",
1534
+ "CommandItem",
1535
+ "CommandSeparator",
1536
+ "CommandShortcut",
1537
+ "CommandDialog"
1538
+ ],
1539
+ importPath: "@/components/ui/command",
1540
+ keyProps: {},
1541
+ usage: `<Command>
1542
+ <CommandInput placeholder="Search..." />
1543
+ <CommandList>
1544
+ <CommandEmpty>No results found.</CommandEmpty>
1545
+ <CommandGroup heading="Actions">
1546
+ <CommandItem>Search</CommandItem>
1547
+ </CommandGroup>
1548
+ </CommandList>
1549
+ </Command>`,
1550
+ antiPatterns: ["NEVER build custom search palette \u2014 use Command/CommandDialog"]
1551
+ },
1552
+ tabs: {
1553
+ name: "Tabs",
1554
+ subcomponents: ["Tabs", "TabsList", "TabsTrigger", "TabsContent"],
1555
+ importPath: "@/components/ui/tabs",
1556
+ keyProps: {
1557
+ defaultValue: "string",
1558
+ orientation: '"horizontal" | "vertical"'
1559
+ },
1560
+ usage: `<Tabs defaultValue="general">
1561
+ <TabsList>
1562
+ <TabsTrigger value="general">General</TabsTrigger>
1563
+ <TabsTrigger value="billing">Billing</TabsTrigger>
1564
+ </TabsList>
1565
+ <TabsContent value="general">General settings...</TabsContent>
1566
+ <TabsContent value="billing">Billing settings...</TabsContent>
1567
+ </Tabs>`,
1568
+ antiPatterns: [
1569
+ "NEVER use Button group for tab switching \u2014 use Tabs",
1570
+ "NEVER use full Sidebar for in-page nav with <= 5 items \u2014 use vertical Tabs"
1571
+ ]
1572
+ },
1573
+ card: {
1574
+ name: "Card",
1575
+ subcomponents: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "CardFooter"],
1576
+ importPath: "@/components/ui/card",
1577
+ keyProps: {},
1578
+ usage: `<Card>
1579
+ <CardHeader>
1580
+ <CardTitle>Title</CardTitle>
1581
+ <CardDescription>Description</CardDescription>
1582
+ </CardHeader>
1583
+ <CardContent>Content</CardContent>
1584
+ <CardFooter>Footer</CardFooter>
1585
+ </Card>`,
1586
+ antiPatterns: []
1587
+ },
1588
+ table: {
1589
+ name: "Table",
1590
+ subcomponents: [
1591
+ "Table",
1592
+ "TableHeader",
1593
+ "TableBody",
1594
+ "TableFooter",
1595
+ "TableRow",
1596
+ "TableHead",
1597
+ "TableCell",
1598
+ "TableCaption"
1599
+ ],
1600
+ importPath: "@/components/ui/table",
1601
+ keyProps: {},
1602
+ usage: `<Table>
1603
+ <TableHeader>
1604
+ <TableRow><TableHead>Name</TableHead></TableRow>
1605
+ </TableHeader>
1606
+ <TableBody>
1607
+ <TableRow><TableCell>Value</TableCell></TableRow>
1608
+ </TableBody>
1609
+ </Table>`,
1610
+ antiPatterns: ["NEVER use native <table> \u2014 use shadcn Table for consistent styling"]
1611
+ },
1612
+ accordion: {
1613
+ name: "Accordion",
1614
+ subcomponents: ["Accordion", "AccordionItem", "AccordionTrigger", "AccordionContent"],
1615
+ importPath: "@/components/ui/accordion",
1616
+ keyProps: {
1617
+ type: '"single" | "multiple"',
1618
+ collapsible: "boolean"
1619
+ },
1620
+ usage: `<Accordion type="single" collapsible>
1621
+ <AccordionItem value="item-1">
1622
+ <AccordionTrigger>Section</AccordionTrigger>
1623
+ <AccordionContent>Content</AccordionContent>
1624
+ </AccordionItem>
1625
+ </Accordion>`,
1626
+ antiPatterns: []
1627
+ }
1628
+ };
1629
+ var componentMetaMap = /* @__PURE__ */ new Map();
1630
+ for (const meta of COMPONENT_REGISTRY) {
1631
+ componentMetaMap.set(meta.id, meta);
1632
+ }
1633
+ function basicAPI(id, name, _category) {
1634
+ return {
1635
+ name,
1636
+ subcomponents: [name],
1637
+ importPath: `@/components/ui/${id}`,
1638
+ keyProps: {},
1639
+ usage: `<${name} />`,
1640
+ antiPatterns: []
1641
+ };
1642
+ }
1643
+ var ShadcnProvider = class {
1644
+ id = "shadcn";
1645
+ async init(projectRoot) {
1646
+ const componentsJsonPath = path.join(projectRoot, "components.json");
1647
+ if (fsExistsSync(componentsJsonPath)) return;
1648
+ const componentsJson = {
1649
+ $schema: "https://ui.shadcn.com/schema.json",
1650
+ style: "new-york",
1651
+ tailwind: {
1652
+ config: "",
1653
+ css: "app/globals.css",
1654
+ baseColor: "neutral",
1655
+ cssVariables: true,
1656
+ prefix: ""
1657
+ },
1658
+ rsc: true,
1659
+ tsx: true,
1660
+ iconLibrary: "lucide",
1661
+ aliases: {
1662
+ components: "@/components",
1663
+ utils: "@/lib/utils",
1664
+ ui: "@/components/ui",
1665
+ lib: "@/lib",
1666
+ hooks: "@/hooks"
1667
+ }
1668
+ };
1669
+ const { writeFileSync: writeFileSync2 } = await import("fs");
1670
+ writeFileSync2(componentsJsonPath, JSON.stringify(componentsJson, null, 2) + "\n");
1671
+ }
1672
+ async install(name, projectRoot, deps = defaultDeps, force = false) {
1673
+ const componentPath = path.join(projectRoot, "components", "ui", `${name}.tsx`);
1674
+ if (!force && deps.existsSync(componentPath)) return;
1675
+ try {
1676
+ await new Promise((resolve4, reject) => {
1677
+ deps.exec(`npx shadcn@latest add ${name} --yes --overwrite`, { cwd: projectRoot, timeout: 15e3 }, (err) => {
1678
+ if (err) reject(err);
1679
+ else resolve4();
1680
+ });
1681
+ });
1682
+ } catch {
1683
+ console.warn(
1684
+ `Could not install ${name} (network error or timeout). Run \`npx shadcn@latest add ${name}\` manually.`
1685
+ );
1686
+ }
1687
+ }
1688
+ async installComponent(id, projectRoot, options) {
1689
+ if (!this.has(id)) return { success: false, componentDef: null };
1690
+ await this.init(projectRoot);
1691
+ const filePath = path.join(projectRoot, "components", "ui", `${toKebab(id)}.tsx`);
1692
+ if (!options?.force && fsExistsSync(filePath)) {
1693
+ return { success: true, componentDef: getShadcnComponent(id) ?? null };
1694
+ }
1695
+ await this.install(id, projectRoot, defaultDeps, !!options?.force);
1696
+ const success = fsExistsSync(filePath);
1697
+ const componentDef = success ? getShadcnComponent(id) ?? null : null;
1698
+ return { success, componentDef };
1699
+ }
1700
+ async installBatch(ids, projectRoot, options, deps = defaultDeps) {
1701
+ const results = /* @__PURE__ */ new Map();
1702
+ const invalidIds = ids.filter((id) => !this.has(id));
1703
+ const validIds = ids.filter((id) => this.has(id));
1704
+ for (const id of invalidIds) {
1705
+ results.set(id, { success: false, componentDef: null });
1706
+ }
1707
+ if (validIds.length === 0) return results;
1708
+ await this.init(projectRoot);
1709
+ const toInstall = [];
1710
+ for (const id of validIds) {
1711
+ const filePath = path.join(projectRoot, "components", "ui", `${toKebab(id)}.tsx`);
1712
+ if (!options?.force && fsExistsSync(filePath)) {
1713
+ results.set(id, { success: true, componentDef: getShadcnComponent(id) ?? null });
1714
+ } else {
1715
+ toInstall.push(id);
1716
+ }
1717
+ }
1718
+ if (toInstall.length === 0) return results;
1719
+ try {
1720
+ await new Promise((resolve4, reject) => {
1721
+ deps.exec(
1722
+ `npx shadcn@latest add ${toInstall.join(" ")} --yes --overwrite`,
1723
+ { cwd: projectRoot, timeout: 3e4 },
1724
+ (err) => {
1725
+ if (err) reject(err);
1726
+ else resolve4();
1727
+ }
1728
+ );
1729
+ });
1730
+ } catch {
1731
+ for (const id of toInstall) {
1732
+ const result = await this.installComponent(id, projectRoot, options);
1733
+ results.set(id, result);
1734
+ }
1735
+ return results;
1736
+ }
1737
+ for (const id of toInstall) {
1738
+ const filePath = path.join(projectRoot, "components", "ui", `${toKebab(id)}.tsx`);
1739
+ const success = fsExistsSync(filePath);
1740
+ results.set(id, {
1741
+ success,
1742
+ componentDef: success ? getShadcnComponent(id) ?? null : null
1743
+ });
1744
+ }
1745
+ return results;
1746
+ }
1747
+ has(name) {
1748
+ return componentMetaMap.has(name);
1749
+ }
1750
+ listNames() {
1751
+ return COMPONENT_REGISTRY.map((c) => c.id);
1752
+ }
1753
+ list() {
1754
+ return COMPONENT_REGISTRY;
1755
+ }
1756
+ getComponentAPI(name) {
1757
+ const meta = componentMetaMap.get(name);
1758
+ if (!meta) return null;
1759
+ if (COMPONENT_APIS[name]) return COMPONENT_APIS[name];
1760
+ return basicAPI(meta.id, meta.name, meta.category);
1761
+ }
1762
+ getCssVariables(tokens) {
1763
+ const fakeConfig = { tokens };
1764
+ return buildCssVariables(fakeConfig);
1765
+ }
1766
+ getThemeBlock(_tokens) {
1767
+ const sidebarVars = [
1768
+ "background",
1769
+ "foreground",
1770
+ "primary",
1771
+ "primary-foreground",
1772
+ "accent",
1773
+ "accent-foreground",
1774
+ "border",
1775
+ "ring"
1776
+ ];
1777
+ const lines = sidebarVars.map((v) => ` --color-sidebar-${v}: var(--sidebar-${v});`);
1778
+ const chartLines = Array.from({ length: 5 }, (_, i) => ` --color-chart-${i + 1}: var(--chart-${i + 1});`);
1779
+ return `@theme inline {
1780
+ ${lines.join("\n")}
1781
+ ${chartLines.join("\n")}
1782
+ }
1783
+ `;
1784
+ }
1785
+ };
1786
+
1787
+ // src/providers/index.ts
1788
+ var _instance = null;
1789
+ function getComponentProvider(_config) {
1790
+ if (!_instance) {
1791
+ _instance = new ShadcnProvider();
1792
+ }
1793
+ return _instance;
1794
+ }
1795
+
1796
+ // src/utils/strings.ts
1797
+ function toKebabCase(str) {
1798
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1799
+ }
1800
+
1801
+ // src/commands/chat/utils.ts
1802
+ import { resolve as resolve2 } from "path";
1803
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1804
+
1805
+ // src/utils/find-config.ts
1806
+ import { existsSync as existsSync3 } from "fs";
1807
+ import { resolve, dirname as dirname2, parse } from "path";
1808
+ import { cwd } from "process";
1809
+ import { tmpdir } from "os";
1810
+ import chalk from "chalk";
1811
+ var VOLATILE_DIRS = ["/tmp", "/private/tmp", "/var/tmp"];
1812
+ function isVolatileDirectory(dir) {
1813
+ const resolved = resolve(dir);
1814
+ const sysTmp = resolve(tmpdir());
1815
+ if (resolved.startsWith(sysTmp)) return true;
1816
+ return VOLATILE_DIRS.some((v) => resolved.startsWith(v));
1817
+ }
1818
+ function warnIfVolatile(projectRoot) {
1819
+ if (isVolatileDirectory(projectRoot)) {
1820
+ console.log("");
1821
+ console.log(chalk.yellow("\u26A0 WARNING: This project is inside a temporary directory."));
1822
+ console.log(chalk.yellow(" Your OS may automatically delete files from this location."));
1823
+ console.log(chalk.dim(` Project path: ${projectRoot}`));
1824
+ console.log(chalk.dim(" Move your project to a permanent location to avoid data loss:"));
1825
+ console.log(chalk.dim(` cp -r "${projectRoot}" ~/projects/
1826
+ `));
1827
+ }
1828
+ }
1829
+ function findConfig(startDir = cwd()) {
1830
+ try {
1831
+ let dir = resolve(startDir);
1832
+ const root = parse(dir).root;
1833
+ while (dir !== root) {
1834
+ const configPath = resolve(dir, "design-system.config.ts");
1835
+ if (existsSync3(configPath)) {
1836
+ return {
1837
+ root: dir,
1838
+ configPath
1839
+ };
1840
+ }
1841
+ const parentDir = dirname2(dir);
1842
+ if (parentDir === dir) {
1843
+ break;
1844
+ }
1845
+ dir = parentDir;
1846
+ }
1847
+ return null;
1848
+ } catch (error) {
1849
+ if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
1850
+ return null;
1851
+ }
1852
+ throw error;
1853
+ }
1854
+ }
1855
+ function getProjectRoot(startDir) {
1856
+ const project = findConfig(startDir);
1857
+ if (!project) {
1858
+ throw new Error("Not a Coherent project");
1859
+ }
1860
+ return project.root;
1861
+ }
1862
+ function exitNotCoherent() {
1863
+ const resolved = resolve(cwd());
1864
+ console.error(chalk.red("\u274C Not a Coherent project"));
1865
+ if (isVolatileDirectory(resolved)) {
1866
+ console.log(chalk.yellow("\u26A0 This directory is inside /tmp \u2014 the OS may have cleaned up your files."));
1867
+ console.log(chalk.dim(" macOS periodically removes files from /tmp. Use a permanent directory next time."));
1868
+ }
1869
+ console.log(chalk.dim("Run coherent init first, or cd into a project directory.\n"));
1870
+ process.exit(1);
1871
+ }
1872
+
1873
+ // src/commands/chat/utils.ts
1874
+ import { DesignSystemManager, loadManifest } from "@getcoherent/core";
1875
+ import chalk2 from "chalk";
1876
+ var MARKETING_ROUTES = /* @__PURE__ */ new Set(["", "landing", "pricing", "about", "contact", "blog", "features"]);
1877
+ var MIN_ANCHOR_PAGE_CODE_CHARS = 120;
1878
+ var AUTH_ROUTE_SLUGS = /* @__PURE__ */ new Set([
1879
+ "login",
1880
+ "signin",
1881
+ "sign-in",
1882
+ "register",
1883
+ "sign-up",
1884
+ "signup",
1885
+ "forgot-password",
1886
+ "reset-password"
1887
+ ]);
1888
+ function inferRouteUsesAuthSegment(route) {
1889
+ const slug = route.replace(/^\//, "").split("/")[0] || "";
1890
+ return AUTH_ROUTE_SLUGS.has(slug);
1891
+ }
1892
+ function readAnchorPageCodeFromDisk(projectRoot, route) {
1893
+ const useAuthSegment = inferRouteUsesAuthSegment(route);
1894
+ const abs = routeToFsPath(projectRoot, route, useAuthSegment);
1895
+ if (!existsSync4(abs)) return null;
1896
+ let code;
1897
+ try {
1898
+ code = readFileSync2(abs, "utf-8");
1899
+ } catch {
1900
+ return null;
1901
+ }
1902
+ if (code.trim().length < MIN_ANCHOR_PAGE_CODE_CHARS) return null;
1903
+ return code;
1904
+ }
1905
+ function isMarketingRoute(route) {
1906
+ const slug = route.replace(/^\//, "").split("/")[0] || "";
1907
+ return MARKETING_ROUTES.has(slug);
1908
+ }
1909
+ function routeToFsPath(projectRoot, route, isAuthOrPlan) {
1910
+ const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
1911
+ const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
1912
+ const slug = route.replace(/^\//, "");
1913
+ if (!slug) return resolve2(projectRoot, "app", "page.tsx");
1914
+ if (isAuth || isAuthRoute(route)) return resolve2(projectRoot, "app", "(auth)", slug || "login", "page.tsx");
1915
+ if (plan) {
1916
+ const group = getPageGroup(route, plan);
1917
+ if (group) return resolve2(projectRoot, "app", `(${group.id})`, slug, "page.tsx");
1918
+ }
1919
+ if (isMarketingRoute(route)) return resolve2(projectRoot, "app", slug, "page.tsx");
1920
+ return resolve2(projectRoot, "app", "(app)", slug, "page.tsx");
1921
+ }
1922
+ function routeToRelPath(route, isAuthOrPlan) {
1923
+ const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
1924
+ const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
1925
+ const slug = route.replace(/^\//, "");
1926
+ if (!slug) return "app/page.tsx";
1927
+ if (isAuth || isAuthRoute(route)) return `app/(auth)/${slug || "login"}/page.tsx`;
1928
+ if (plan) {
1929
+ const group = getPageGroup(route, plan);
1930
+ if (group) return `app/(${group.id})/${slug}/page.tsx`;
1931
+ }
1932
+ if (isMarketingRoute(route)) return `app/${slug}/page.tsx`;
1933
+ return `app/(app)/${slug}/page.tsx`;
1934
+ }
1935
+ var AUTH_SYNONYMS = {
1936
+ "/register": "/signup",
1937
+ "/registration": "/signup",
1938
+ "/sign-up": "/signup",
1939
+ "/signin": "/login",
1940
+ "/sign-in": "/login"
1941
+ };
1942
+ function deduplicatePages(pages) {
1943
+ const canonicalize = (route) => AUTH_SYNONYMS[route] || route;
1944
+ const normalize = (route) => canonicalize(route).replace(/\/$/, "");
1945
+ const seen = /* @__PURE__ */ new Map();
1946
+ return pages.filter((page, idx) => {
1947
+ const norm = normalize(page.route);
1948
+ if (seen.has(norm)) return false;
1949
+ seen.set(norm, idx);
1950
+ return true;
1951
+ });
1952
+ }
1953
+ function extractComponentIdsFromCode(code) {
1954
+ const ids = /* @__PURE__ */ new Set();
1955
+ const allMatches = code.matchAll(/@\/components\/((?:ui\/)?[a-z0-9-]+)/g);
1956
+ for (const m of allMatches) {
1957
+ if (!m[1]) continue;
1958
+ let id = m[1];
1959
+ if (id.startsWith("ui/")) id = id.slice(3);
1960
+ if (id === "shared" || id.startsWith("shared/")) continue;
1961
+ if (id) ids.add(id);
1962
+ }
1963
+ return ids;
1964
+ }
1965
+ async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, manifest, plan) {
1966
+ const reusable = manifest.shared.filter((e) => e.type !== "layout");
1967
+ if (reusable.length === 0) return;
1968
+ const plannedForPage = plan ? new Set(plan.sharedComponents.filter((c) => c.usedBy.includes(route)).map((c) => c.name)) : null;
1969
+ for (const e of reusable) {
1970
+ if (plannedForPage && !plannedForPage.has(e.name)) continue;
1971
+ const kebab = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
1972
+ const hasImport = pageCode.includes(`@/components/shared/${kebab}`);
1973
+ if (hasImport) continue;
1974
+ if (plannedForPage) {
1975
+ console.log(
1976
+ chalk2.yellow(
1977
+ `
1978
+ \u26A0 Page "${pageName}" should use shared component ${e.name} (per architecture plan) but it's not imported. Import from @/components/shared/${kebab}`
1979
+ )
1980
+ );
1981
+ continue;
1982
+ }
1983
+ const sameNameAsTag = new RegExp(`<\\/?${e.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s>]`).test(pageCode);
1984
+ if (sameNameAsTag) {
1985
+ console.log(
1986
+ chalk2.yellow(
1987
+ `
1988
+ \u26A0 Page "${pageName}" contains inline code similar to ${e.id} (${e.name}). Consider using the shared component instead.`
1989
+ )
1990
+ );
1991
+ continue;
1992
+ }
1993
+ try {
1994
+ const fullPath = resolve2(projectRoot, e.file);
1995
+ const sharedSnippet = (await readFile2(fullPath)).slice(0, 600);
1996
+ const sharedTokens = new Set(sharedSnippet.match(/\b[a-zA-Z0-9-]{4,}\b/g) ?? []);
1997
+ const pageTokens = pageCode.match(/\b[a-zA-Z0-9-]+\b/g) ?? [];
1998
+ let overlap = 0;
1999
+ for (const t of sharedTokens) {
2000
+ if (pageTokens.includes(t)) overlap++;
2001
+ }
2002
+ const overlapRatio = sharedTokens.size > 0 ? overlap / sharedTokens.size : 0;
2003
+ if (overlap >= 25 && overlapRatio >= 0.7) {
2004
+ console.log(
2005
+ chalk2.yellow(
2006
+ `
2007
+ \u26A0 Page "${pageName}" contains inline code similar to ${e.id} (${e.name}). Consider using the shared component instead.`
2008
+ )
2009
+ );
2010
+ }
2011
+ } catch {
2012
+ }
2013
+ }
2014
+ }
2015
+ async function loadConfig(configPath) {
2016
+ if (!existsSync4(configPath)) {
2017
+ throw new Error(
2018
+ `Design system config not found at ${configPath}
2019
+ Run "coherent init" first to create a project.`
2020
+ );
2021
+ }
2022
+ const manager = new DesignSystemManager(configPath);
2023
+ await manager.load();
2024
+ return manager.getConfig();
2025
+ }
2026
+ function requireProject() {
2027
+ const project = findConfig();
2028
+ if (!project) {
2029
+ exitNotCoherent();
2030
+ }
2031
+ warnIfVolatile(project.root);
2032
+ return project;
2033
+ }
2034
+ async function resolveTargetFlags(message, options, config, projectRoot) {
2035
+ if (options.component) {
2036
+ const manifest = await loadManifest(projectRoot);
2037
+ const target = options.component;
2038
+ const entry = manifest.shared.find(
2039
+ (s) => s.name.toLowerCase() === target.toLowerCase() || s.id.toLowerCase() === target.toLowerCase()
2040
+ );
2041
+ if (entry) {
2042
+ const filePath = resolve2(projectRoot, entry.file);
2043
+ let currentCode = "";
2044
+ if (existsSync4(filePath)) {
2045
+ currentCode = readFileSync2(filePath, "utf-8");
2046
+ }
2047
+ const codeSnippet = currentCode ? `
2048
+
2049
+ Current code of ${entry.name}:
2050
+ \`\`\`tsx
2051
+ ${currentCode}
2052
+ \`\`\`` : "";
2053
+ return `Modify the shared component ${entry.name} (${entry.id}, file: ${entry.file}): ${message}. Read the current code below and apply the requested changes. Return the full updated component code as pageCode.${codeSnippet}`;
2054
+ }
2055
+ console.log(chalk2.yellow(`
2056
+ \u26A0\uFE0F Component "${target}" not found in shared components.`));
2057
+ console.log(chalk2.dim(" Available: " + manifest.shared.map((s) => `${s.id} ${s.name}`).join(", ")));
2058
+ console.log(chalk2.dim(" Proceeding with message as-is...\n"));
2059
+ }
2060
+ if (options.page) {
2061
+ const target = options.page;
2062
+ const page = config.pages.find(
2063
+ (p) => p.name.toLowerCase() === target.toLowerCase() || p.id.toLowerCase() === target.toLowerCase() || p.route === target || p.route === "/" + target
2064
+ );
2065
+ if (page) {
2066
+ const relPath = page.route === "/" ? "app/page.tsx" : `app${page.route}/page.tsx`;
2067
+ const filePath = resolve2(projectRoot, relPath);
2068
+ let currentCode = "";
2069
+ if (existsSync4(filePath)) {
2070
+ currentCode = readFileSync2(filePath, "utf-8");
2071
+ }
2072
+ const codeSnippet = currentCode ? `
2073
+
2074
+ Current code of ${page.name} page:
2075
+ \`\`\`tsx
2076
+ ${currentCode}
2077
+ \`\`\`` : "";
2078
+ return `Update page "${page.name}" (id: ${page.id}, route: ${page.route}, file: ${relPath}): ${message}. Read the current code below and apply the requested changes.${codeSnippet}`;
2079
+ }
2080
+ console.log(chalk2.yellow(`
2081
+ \u26A0\uFE0F Page "${target}" not found.`));
2082
+ console.log(chalk2.dim(" Available: " + config.pages.map((p) => `${p.id} (${p.route})`).join(", ")));
2083
+ console.log(chalk2.dim(" Proceeding with message as-is...\n"));
2084
+ }
2085
+ if (options.token) {
2086
+ const target = options.token;
2087
+ return `Change design token "${target}": ${message}. Update the token value in design-system.config.ts and ensure globals.css reflects the change.`;
2088
+ }
2089
+ return message;
2090
+ }
2091
+
2092
+ // src/commands/chat/code-generator.ts
2093
+ async function validateAndFixGeneratedCode(projectRoot, code, options = {}) {
2094
+ const fixes = [];
2095
+ let fixed = fixEscapedClosingQuotes(code);
2096
+ fixed = fixUnescapedLtInJsx(fixed);
2097
+ if (fixed !== code) fixes.push("Fixed syntax issues");
2098
+ const beforeMeta = fixed;
2099
+ fixed = options.isPage !== false ? sanitizeMetadataStrings(ensureUseClientIfNeeded(fixed)) : ensureUseClientIfNeeded(fixed);
2100
+ if (fixed !== beforeMeta) fixes.push("Fixed metadata / use client");
2101
+ const missing = findMissingPackagesInCode(fixed, projectRoot);
2102
+ if (missing.length > 0) {
2103
+ const ok = await installPackages(projectRoot, missing);
2104
+ if (ok) fixes.push(`Installed: ${missing.join(", ")}`);
2105
+ }
2106
+ return { fixedCode: fixed, fixes };
2107
+ }
2108
+ async function ensureComponentsInstalled(componentIds, cm, dsm, pm, projectRoot) {
2109
+ const installed = [];
2110
+ const ids = Array.from(componentIds);
2111
+ const provider = getComponentProvider();
2112
+ for (const componentId of ids) {
2113
+ const isRegistered = !!cm.read(componentId);
2114
+ const fileName = toKebabCase(componentId) + ".tsx";
2115
+ const filePath = resolve3(projectRoot, "components", "ui", fileName);
2116
+ const fileExists = existsSync5(filePath);
2117
+ if (isRegistered && fileExists) continue;
2118
+ const result = await provider.installComponent(componentId, projectRoot);
2119
+ if (result.success && result.componentDef) {
2120
+ if (!isRegistered) {
2121
+ const regResult = await cm.register(result.componentDef);
2122
+ if (regResult.success) {
2123
+ dsm.updateConfig(regResult.config);
2124
+ cm.updateConfig(regResult.config);
2125
+ pm.updateConfig(regResult.config);
2126
+ }
2127
+ }
2128
+ installed.push(result.componentDef.id);
2129
+ }
2130
+ }
2131
+ return { installed };
2132
+ }
2133
+ async function regenerateComponent(componentId, config, projectRoot) {
2134
+ const component = config.components.find((c) => c.id === componentId);
2135
+ if (!component) return;
2136
+ if (component.source === "shadcn") return;
2137
+ const generator = new ComponentGenerator(config);
2138
+ const code = await generator.generate(component);
2139
+ const fileName = toKebabCase(component.name) + ".tsx";
2140
+ const filePath = resolve3(projectRoot, "components", "ui", fileName);
2141
+ await writeFile2(filePath, code);
2142
+ }
2143
+ async function regeneratePage(pageId, config, projectRoot) {
2144
+ const page = config.pages.find((p) => p.id === pageId);
2145
+ if (!page) return;
2146
+ if (page.generatedWithPageCode) return;
2147
+ const generator = new PageGenerator(config);
2148
+ const appType = config.settings.appType || "multi-page";
2149
+ const code = await generator.generate(page, appType);
2150
+ const route = page.route || "/";
2151
+ const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
2152
+ const { loadPlan: loadPlanForPath } = await import("./plan-generator-HRULTJ55.js");
2153
+ const planForPath = loadPlanForPath(projectRoot);
2154
+ const filePath = routeToFsPath(projectRoot, route, planForPath || isAuth);
2155
+ await mkdir3(dirname3(filePath), { recursive: true });
2156
+ await writeFile2(filePath, code);
2157
+ }
2158
+ async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
2159
+ const filePath = resolve3(projectRoot, componentFile);
2160
+ if (!existsSync5(filePath)) return true;
2161
+ const storedHash = storedHashes[componentFile];
2162
+ if (!storedHash) return true;
2163
+ const edited = await isManuallyEdited(filePath, storedHash);
2164
+ if (edited) {
2165
+ console.log(chalk3.yellow(` \u26A0 Skipping ${componentFile} \u2014 manually edited since last generation`));
2166
+ }
2167
+ return !edited;
2168
+ }
2169
+ async function regenerateLayout(config, projectRoot, options = {
2170
+ navChanged: false
2171
+ }) {
2172
+ const appType = config.settings.appType || "multi-page";
2173
+ const generator = new PageGenerator(config);
2174
+ const initialized = config.settings.initialized !== false;
2175
+ const hashes = options.storedHashes ?? {};
2176
+ if (!initialized) {
2177
+ const layout = config.pages[0]?.layout || "centered";
2178
+ const code = await generator.generateLayout(layout, appType, { skipNav: true });
2179
+ await writeFile2(resolve3(projectRoot, "app", "layout.tsx"), code);
2180
+ }
2181
+ if (config.navigation?.enabled && appType === "multi-page") {
2182
+ const navType = config.navigation.type || "header";
2183
+ const shouldRegenShared = !initialized || options.navChanged;
2184
+ if (shouldRegenShared) {
2185
+ if (navType === "header" || navType === "both") {
2186
+ if (await canOverwriteShared(projectRoot, "components/shared/header.tsx", hashes)) {
2187
+ const headerCode = generator.generateSharedHeaderCode();
2188
+ await generateSharedComponent(projectRoot, {
2189
+ name: "Header",
2190
+ type: "layout",
2191
+ code: headerCode,
2192
+ description: "Main site header with navigation and theme toggle",
2193
+ usedIn: ["app/layout.tsx"],
2194
+ overwrite: true
2195
+ });
2196
+ }
2197
+ }
2198
+ if (await canOverwriteShared(projectRoot, "components/shared/footer.tsx", hashes)) {
2199
+ const footerCode = generator.generateSharedFooterCode();
2200
+ await generateSharedComponent(projectRoot, {
2201
+ name: "Footer",
2202
+ type: "layout",
2203
+ code: footerCode,
2204
+ description: "Site footer",
2205
+ usedIn: ["app/layout.tsx"],
2206
+ overwrite: true
2207
+ });
2208
+ }
2209
+ if (navType === "sidebar" || navType === "both") {
2210
+ if (await canOverwriteShared(projectRoot, "components/shared/sidebar.tsx", hashes)) {
2211
+ const sidebarCode = generator.generateSharedSidebarCode();
2212
+ await generateSharedComponent(projectRoot, {
2213
+ name: "AppSidebar",
2214
+ type: "layout",
2215
+ code: sidebarCode,
2216
+ description: "Application sidebar using shadcn/ui Sidebar components",
2217
+ usedIn: ["app/(app)/layout.tsx"],
2218
+ overwrite: true
2219
+ });
2220
+ }
2221
+ }
2222
+ }
2223
+ }
2224
+ try {
2225
+ await integrateSharedLayoutIntoRootLayout(projectRoot);
2226
+ await ensureAuthRouteGroup(projectRoot);
2227
+ await ensureAppRouteGroupLayout(
2228
+ projectRoot,
2229
+ config.navigation?.type,
2230
+ options.navChanged,
2231
+ options.groupLayouts ?? config.groupLayouts
2232
+ );
2233
+ } catch (err) {
2234
+ if (process.env.COHERENT_DEBUG === "1") {
2235
+ console.log(chalk3.dim("Layout integration warning:", err));
2236
+ }
2237
+ }
2238
+ }
2239
+ async function scanAndInstallSharedDeps(projectRoot) {
2240
+ const sharedDir = resolve3(projectRoot, "components", "shared");
2241
+ if (!existsSync5(sharedDir)) return [];
2242
+ const files = readdirSync(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
2243
+ const installed = [];
2244
+ const provider = getComponentProvider();
2245
+ for (const file of files) {
2246
+ const code = readFileSync3(resolve3(sharedDir, file), "utf-8");
2247
+ const importMatches = [...code.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g)];
2248
+ for (const [, componentId] of importMatches) {
2249
+ const uiPath = resolve3(projectRoot, "components", "ui", `${componentId}.tsx`);
2250
+ if (!existsSync5(uiPath) && provider.has(componentId)) {
2251
+ try {
2252
+ await provider.installComponent(componentId, projectRoot);
2253
+ installed.push(componentId);
2254
+ } catch {
2255
+ }
2256
+ }
2257
+ }
2258
+ }
2259
+ return [...new Set(installed)];
2260
+ }
2261
+ async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false, groupLayouts) {
2262
+ const effectiveNavType = groupLayouts?.["app"] || navType;
2263
+ const layoutPath = resolve3(projectRoot, "app", "(app)", "layout.tsx");
2264
+ if (existsSync5(layoutPath) && !forceUpdate) return;
2265
+ const { mkdir: mkdirAsync } = await import("fs/promises");
2266
+ await mkdirAsync(resolve3(projectRoot, "app", "(app)"), { recursive: true });
2267
+ const code = buildAppLayoutCode(effectiveNavType);
2268
+ await writeFile2(layoutPath, code);
2269
+ }
2270
+ function buildAppLayoutCode(navType) {
2271
+ const hasSidebar = navType === "sidebar" || navType === "both";
2272
+ if (hasSidebar) {
2273
+ return `import { AppSidebar } from '@/components/shared/sidebar'
2274
+ import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar'
2275
+
2276
+ export default function AppLayout({
2277
+ children,
2278
+ }: {
2279
+ children: React.ReactNode
2280
+ }) {
2281
+ return (
2282
+ <SidebarProvider>
2283
+ <AppSidebar />
2284
+ <SidebarInset>
2285
+ <main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
2286
+ {children}
2287
+ </main>
2288
+ </SidebarInset>
2289
+ </SidebarProvider>
2290
+ )
2291
+ }
2292
+ `;
2293
+ }
2294
+ return `export default function AppLayout({
2295
+ children,
2296
+ }: {
2297
+ children: React.ReactNode
2298
+ }) {
2299
+ return (
2300
+ <main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
2301
+ {children}
2302
+ </main>
2303
+ )
2304
+ }
2305
+ `;
2306
+ }
2307
+ function buildGroupLayoutCode(layout, _pages) {
2308
+ if (layout === "sidebar" || layout === "both") {
2309
+ return `import { AppSidebar } from '@/components/shared/sidebar'
2310
+ import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar'
2311
+
2312
+ export default function GroupLayout({
2313
+ children,
2314
+ }: {
2315
+ children: React.ReactNode
2316
+ }) {
2317
+ return (
2318
+ <SidebarProvider>
2319
+ <AppSidebar />
2320
+ <SidebarInset>
2321
+ <main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
2322
+ {children}
2323
+ </main>
2324
+ </SidebarInset>
2325
+ </SidebarProvider>
2326
+ )
2327
+ }
2328
+ `;
2329
+ }
2330
+ if (layout === "none") {
2331
+ return `export default function GroupLayout({
2332
+ children,
2333
+ }: {
2334
+ children: React.ReactNode
2335
+ }) {
2336
+ return (
2337
+ <div className="min-h-svh flex items-center justify-center p-4">
2338
+ {children}
2339
+ </div>
2340
+ )
2341
+ }
2342
+ `;
2343
+ }
2344
+ return `export default function GroupLayout({
2345
+ children,
2346
+ }: {
2347
+ children: React.ReactNode
2348
+ }) {
2349
+ return (
2350
+ <main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
2351
+ {children}
2352
+ </main>
2353
+ )
2354
+ }
2355
+ `;
2356
+ }
2357
+ async function ensurePlanGroupLayouts(projectRoot, plan, storedHashes = {}, config) {
2358
+ const { mkdir: mkdirAsync } = await import("fs/promises");
2359
+ const { createHash: createHash2 } = await import("crypto");
2360
+ for (const group of plan.groups) {
2361
+ const groupDir = resolve3(projectRoot, "app", `(${group.id})`);
2362
+ await mkdirAsync(groupDir, { recursive: true });
2363
+ const layoutPath = resolve3(groupDir, "layout.tsx");
2364
+ const relPath = `app/(${group.id})/layout.tsx`;
2365
+ if (existsSync5(layoutPath)) {
2366
+ const currentContent = readFileSync3(layoutPath, "utf-8");
2367
+ const currentHash = createHash2("md5").update(currentContent).digest("hex");
2368
+ const storedHash = storedHashes[relPath];
2369
+ if (storedHash && storedHash !== currentHash) {
2370
+ continue;
2371
+ }
2372
+ }
2373
+ const code = buildGroupLayoutCode(group.layout, group.pages);
2374
+ await writeFile2(layoutPath, code);
2375
+ }
2376
+ if (config) {
2377
+ const layouts = {};
2378
+ for (const group of plan.groups) {
2379
+ layouts[group.id] = group.layout;
2380
+ }
2381
+ config.groupLayouts = layouts;
2382
+ }
2383
+ }
2384
+ async function regenerateFiles(modified, config, projectRoot, options = { navChanged: false }) {
2385
+ const componentIds = /* @__PURE__ */ new Set();
2386
+ const pageIds = /* @__PURE__ */ new Set();
2387
+ for (const item of modified) {
2388
+ if (item.startsWith("component:")) {
2389
+ componentIds.add(item.replace("component:", ""));
2390
+ } else if (item.startsWith("page:")) {
2391
+ pageIds.add(item.replace("page:", ""));
2392
+ }
2393
+ }
2394
+ if (config.navigation?.enabled && modified.length > 0) {
2395
+ await regenerateLayout(config, projectRoot, {
2396
+ navChanged: options.navChanged,
2397
+ storedHashes: options.storedHashes
2398
+ });
2399
+ const sharedInstalled = await scanAndInstallSharedDeps(projectRoot);
2400
+ if (sharedInstalled.length > 0 && process.env.COHERENT_DEBUG === "1") {
2401
+ console.log(chalk3.dim(` Auto-installed shared deps: ${sharedInstalled.join(", ")}`));
2402
+ }
2403
+ }
2404
+ if (componentIds.size > 0) {
2405
+ const twGen = new TailwindConfigGenerator(config);
2406
+ const twPath = resolve3(projectRoot, "tailwind.config.ts");
2407
+ const twCjsPath = resolve3(projectRoot, "tailwind.config.cjs");
2408
+ if (existsSync5(twPath)) {
2409
+ await writeFile2(twPath, await twGen.generate());
2410
+ } else if (existsSync5(twCjsPath)) {
2411
+ await writeFile2(twCjsPath, await twGen.generateCjs());
2412
+ }
2413
+ }
2414
+ for (const componentId of componentIds) {
2415
+ await regenerateComponent(componentId, config, projectRoot);
2416
+ }
2417
+ const pageCodeIds = new Set(
2418
+ config.pages.filter((p) => p.generatedWithPageCode).map((p) => p.id)
2419
+ );
2420
+ for (const pageId of pageIds) {
2421
+ if (pageCodeIds.has(pageId)) continue;
2422
+ await regeneratePage(pageId, config, projectRoot);
2423
+ }
2424
+ }
2425
+
2426
+ export {
2427
+ warnIfVolatile,
2428
+ findConfig,
2429
+ getProjectRoot,
2430
+ exitNotCoherent,
2431
+ readFile2 as readFile,
2432
+ writeFile2 as writeFile,
2433
+ fileExistsAsync,
2434
+ acquireProjectLock,
2435
+ getShadcnComponent,
2436
+ listShadcnComponents,
2437
+ getComponentProvider,
2438
+ toKebabCase,
2439
+ isAuthRoute,
2440
+ detectPageType,
2441
+ expandPageRequest,
2442
+ ensureAuthRouteGroup,
2443
+ readAnchorPageCodeFromDisk,
2444
+ isMarketingRoute,
2445
+ routeToFsPath,
2446
+ routeToRelPath,
2447
+ AUTH_SYNONYMS,
2448
+ deduplicatePages,
2449
+ extractComponentIdsFromCode,
2450
+ warnInlineDuplicates,
2451
+ loadConfig,
2452
+ requireProject,
2453
+ resolveTargetFlags,
2454
+ computeFileHash,
2455
+ loadHashes,
2456
+ saveHashes,
2457
+ validateAndFixGeneratedCode,
2458
+ ensureComponentsInstalled,
2459
+ regenerateComponent,
2460
+ regeneratePage,
2461
+ regenerateLayout,
2462
+ scanAndInstallSharedDeps,
2463
+ ensureAppRouteGroupLayout,
2464
+ buildAppLayoutCode,
2465
+ buildGroupLayoutCode,
2466
+ ensurePlanGroupLayouts,
2467
+ regenerateFiles
2468
+ };