@getcoherent/cli 0.5.13 → 0.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergei Kovtun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -357,6 +357,49 @@ Requirements:
|
|
|
357
357
|
if (content.type !== "text") throw new Error("Unexpected response type");
|
|
358
358
|
return content.text.trim().replace(/^```(?:tsx?|jsx?)\s*/i, "").replace(/\s*```$/i, "");
|
|
359
359
|
}
|
|
360
|
+
async extractSharedComponents(pageCode, reservedNames, existingSharedNames) {
|
|
361
|
+
try {
|
|
362
|
+
const response = await this.client.messages.create({
|
|
363
|
+
model: this.defaultModel,
|
|
364
|
+
max_tokens: 16384,
|
|
365
|
+
messages: [
|
|
366
|
+
{
|
|
367
|
+
role: "user",
|
|
368
|
+
content: `Analyze this page and extract reusable components.
|
|
369
|
+
|
|
370
|
+
PAGE CODE:
|
|
371
|
+
${pageCode}
|
|
372
|
+
|
|
373
|
+
Rules:
|
|
374
|
+
- Extract 1-5 components maximum
|
|
375
|
+
- Each component must be \u226510 lines of meaningful JSX
|
|
376
|
+
- Output complete, self-contained TypeScript modules with:
|
|
377
|
+
- "use client" directive (if hooks or event handlers are used)
|
|
378
|
+
- All necessary imports (shadcn/ui from @/components/ui/*, lucide-react, next/link, etc.)
|
|
379
|
+
- A typed props interface exported as a named type
|
|
380
|
+
- A named export function (not default export)
|
|
381
|
+
- Do NOT extract: the entire page, trivial wrappers, layout components (header, footer, nav)
|
|
382
|
+
- Do NOT use these names (reserved for shadcn/ui): ${reservedNames.join(", ")}
|
|
383
|
+
- Do NOT use these names (already shared): ${existingSharedNames.join(", ")}
|
|
384
|
+
- Look for: cards with icon+title+description, pricing tiers, testimonial blocks, stat displays, CTA sections
|
|
385
|
+
|
|
386
|
+
Each component object: "name" (PascalCase), "type" ("section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
387
|
+
|
|
388
|
+
If no repeating patterns found: { "components": [] }`
|
|
389
|
+
}
|
|
390
|
+
],
|
|
391
|
+
system: "You are a React/Next.js component extraction specialist. Analyze page code and identify reusable UI patterns that can be extracted into shared components. Return ONLY valid JSON. No markdown fencing, no explanation outside the JSON object."
|
|
392
|
+
});
|
|
393
|
+
const content = response.content[0];
|
|
394
|
+
if (content.type !== "text") return { components: [] };
|
|
395
|
+
const jsonText = this.extractJSON(content.text);
|
|
396
|
+
const parsed = JSON.parse(jsonText);
|
|
397
|
+
const components = Array.isArray(parsed.components) ? parsed.components : [];
|
|
398
|
+
return { components };
|
|
399
|
+
} catch {
|
|
400
|
+
return { components: [] };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
360
403
|
};
|
|
361
404
|
export {
|
|
362
405
|
ClaudeClient
|
package/dist/index.js
CHANGED
|
@@ -3750,11 +3750,11 @@ import { resolve as resolve9, relative as relative2, join as join11 } from "path
|
|
|
3750
3750
|
import { existsSync as existsSync16, readFileSync as readFileSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync2 } from "fs";
|
|
3751
3751
|
import {
|
|
3752
3752
|
DesignSystemManager as DesignSystemManager7,
|
|
3753
|
-
ComponentManager as
|
|
3753
|
+
ComponentManager as ComponentManager5,
|
|
3754
3754
|
PageManager as PageManager3,
|
|
3755
3755
|
CLI_VERSION as CLI_VERSION2,
|
|
3756
3756
|
getTemplateForPageType as getTemplateForPageType2,
|
|
3757
|
-
loadManifest as
|
|
3757
|
+
loadManifest as loadManifest8,
|
|
3758
3758
|
saveManifest as saveManifest2
|
|
3759
3759
|
} from "@getcoherent/core";
|
|
3760
3760
|
|
|
@@ -3831,7 +3831,7 @@ Please set ${envVar} in your environment or .env file.`);
|
|
|
3831
3831
|
}
|
|
3832
3832
|
if (preferredProvider === "openai") {
|
|
3833
3833
|
try {
|
|
3834
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3834
|
+
const { OpenAIClient } = await import("./openai-provider-FSXSVEYD.js");
|
|
3835
3835
|
return await OpenAIClient.create(apiKey2, config2?.model);
|
|
3836
3836
|
} catch (error) {
|
|
3837
3837
|
if (error.message?.includes("not installed")) {
|
|
@@ -3845,7 +3845,7 @@ Error: ${error.message}`
|
|
|
3845
3845
|
);
|
|
3846
3846
|
}
|
|
3847
3847
|
} else {
|
|
3848
|
-
const { ClaudeClient } = await import("./claude-
|
|
3848
|
+
const { ClaudeClient } = await import("./claude-RFHVT7RC.js");
|
|
3849
3849
|
return ClaudeClient.create(apiKey2, config2?.model);
|
|
3850
3850
|
}
|
|
3851
3851
|
}
|
|
@@ -3858,7 +3858,7 @@ Error: ${error.message}`
|
|
|
3858
3858
|
switch (provider) {
|
|
3859
3859
|
case "openai":
|
|
3860
3860
|
try {
|
|
3861
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3861
|
+
const { OpenAIClient } = await import("./openai-provider-FSXSVEYD.js");
|
|
3862
3862
|
return await OpenAIClient.create(apiKey, config2?.model);
|
|
3863
3863
|
} catch (error) {
|
|
3864
3864
|
if (error.message?.includes("not installed")) {
|
|
@@ -3872,7 +3872,7 @@ Error: ${error.message}`
|
|
|
3872
3872
|
);
|
|
3873
3873
|
}
|
|
3874
3874
|
case "claude":
|
|
3875
|
-
const { ClaudeClient } = await import("./claude-
|
|
3875
|
+
const { ClaudeClient } = await import("./claude-RFHVT7RC.js");
|
|
3876
3876
|
return ClaudeClient.create(apiKey, config2?.model);
|
|
3877
3877
|
default:
|
|
3878
3878
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
@@ -7395,6 +7395,10 @@ function applyDefaults(request) {
|
|
|
7395
7395
|
return request;
|
|
7396
7396
|
}
|
|
7397
7397
|
|
|
7398
|
+
// src/commands/chat/split-generator.ts
|
|
7399
|
+
import { z } from "zod";
|
|
7400
|
+
import { loadManifest as loadManifest5, generateSharedComponent as generateSharedComponent2 } from "@getcoherent/core";
|
|
7401
|
+
|
|
7398
7402
|
// src/utils/page-analyzer.ts
|
|
7399
7403
|
var FORM_COMPONENTS = /* @__PURE__ */ new Set(["Input", "Textarea", "Label", "Select", "Checkbox", "Switch"]);
|
|
7400
7404
|
var VISUAL_WORDS = /\b(grid lines?|glow|radial|gradient|blur|shadow|overlay|animation|particles?|dots?|vertical|horizontal|decorat|behind|background|divider|spacer|wrapper|container|inner|outer|absolute|relative|translate|opacity|z-index|transition)\b/i;
|
|
@@ -7589,9 +7593,20 @@ function parseNavTypeFromPlan(planResult) {
|
|
|
7589
7593
|
}
|
|
7590
7594
|
return "header";
|
|
7591
7595
|
}
|
|
7596
|
+
function buildSharedComponentsSummary(manifest) {
|
|
7597
|
+
if (manifest.shared.length === 0) return void 0;
|
|
7598
|
+
return manifest.shared.map((e) => {
|
|
7599
|
+
const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
7600
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
7601
|
+
const propsLine = e.propsInterface ? `
|
|
7602
|
+
Props: ${e.propsInterface}` : "";
|
|
7603
|
+
return ` ${e.id} ${e.name} (${e.type})${desc}
|
|
7604
|
+
Import: @/components/shared/${importPath}${propsLine}`;
|
|
7605
|
+
}).join("\n");
|
|
7606
|
+
}
|
|
7592
7607
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7593
7608
|
let pageNames = [];
|
|
7594
|
-
spinner.start("Phase 1/
|
|
7609
|
+
spinner.start("Phase 1/5 \u2014 Planning pages...");
|
|
7595
7610
|
try {
|
|
7596
7611
|
const planResult = await parseModification(message, modCtx, provider, { ...parseOpts, planOnly: true });
|
|
7597
7612
|
const pageReqs = planResult.requests.filter((r) => r.type === "add-page");
|
|
@@ -7644,7 +7659,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7644
7659
|
const allRoutes = pageNames.map((p) => p.route).join(", ");
|
|
7645
7660
|
const allPagesList = pageNames.map((p) => `${p.name} (${p.route})`).join(", ");
|
|
7646
7661
|
const inferredNote = inferred.length > 0 ? ` (${inferred.length} auto-inferred)` : "";
|
|
7647
|
-
spinner.succeed(`Phase 1/
|
|
7662
|
+
spinner.succeed(`Phase 1/5 \u2014 Found ${pageNames.length} pages${inferredNote}: ${allPagesList}`);
|
|
7648
7663
|
const homeIdx = pageNames.findIndex((p) => p.route === "/");
|
|
7649
7664
|
const homePage = homeIdx !== -1 ? pageNames[homeIdx] : pageNames[0];
|
|
7650
7665
|
const remainingPages = pageNames.filter((_, i) => i !== (homeIdx !== -1 ? homeIdx : 0));
|
|
@@ -7657,12 +7672,12 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7657
7672
|
if (existingCode) {
|
|
7658
7673
|
reusedExistingAnchor = true;
|
|
7659
7674
|
homePageCode = existingCode;
|
|
7660
|
-
spinner.start(`Phase 2/
|
|
7661
|
-
spinner.succeed(`Phase 2/
|
|
7675
|
+
spinner.start(`Phase 2/5 \u2014 Loading ${homePage.name} from disk (style anchor)...`);
|
|
7676
|
+
spinner.succeed(`Phase 2/5 \u2014 Reused existing ${homePage.name} page (skipped AI regeneration)`);
|
|
7662
7677
|
}
|
|
7663
7678
|
}
|
|
7664
7679
|
if (!reusedExistingAnchor) {
|
|
7665
|
-
spinner.start(`Phase 2/
|
|
7680
|
+
spinner.start(`Phase 2/5 \u2014 Generating ${homePage.name} page (sets design direction)...`);
|
|
7666
7681
|
try {
|
|
7667
7682
|
const homeResult = await parseModification(
|
|
7668
7683
|
`Create ONE page called "${homePage.name}" at route "${homePage.route}". Context: ${message}. This REPLACES the default placeholder page \u2014 generate a complete, content-rich landing page for the project described above. Generate complete pageCode. Include a branded site-wide <header> with navigation links to ALL these pages: ${allPagesList}. Use these EXACT routes in navigation: ${allRoutes}. Include a <footer> at the bottom. Make it visually polished \u2014 this page sets the design direction for the entire site. Do not generate other pages.`,
|
|
@@ -7684,21 +7699,40 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7684
7699
|
changes: { id: homePage.id, name: homePage.name, route: homePage.route }
|
|
7685
7700
|
};
|
|
7686
7701
|
}
|
|
7687
|
-
spinner.succeed(`Phase 2/
|
|
7702
|
+
spinner.succeed(`Phase 2/5 \u2014 ${homePage.name} page generated`);
|
|
7688
7703
|
}
|
|
7689
|
-
spinner.start("Phase 3/
|
|
7704
|
+
spinner.start("Phase 3/5 \u2014 Extracting design patterns...");
|
|
7690
7705
|
const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
|
|
7691
7706
|
if (styleContext) {
|
|
7692
7707
|
const lineCount = styleContext.split("\n").length - 1;
|
|
7693
7708
|
const source = reusedExistingAnchor ? `${homePage.name} (existing file)` : homePage.name;
|
|
7694
|
-
spinner.succeed(`Phase 3/
|
|
7709
|
+
spinner.succeed(`Phase 3/5 \u2014 Extracted ${lineCount} style patterns from ${source}`);
|
|
7695
7710
|
} else {
|
|
7696
|
-
spinner.succeed("Phase 3/
|
|
7711
|
+
spinner.succeed("Phase 3/5 \u2014 No style patterns extracted (anchor page had no code)");
|
|
7712
|
+
}
|
|
7713
|
+
if (remainingPages.length >= 2 && homePageCode && projectRoot) {
|
|
7714
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7715
|
+
const shouldSkip = reusedExistingAnchor && manifest.shared.some((e) => e.type !== "layout");
|
|
7716
|
+
if (!shouldSkip) {
|
|
7717
|
+
spinner.start("Phase 3.5/5 \u2014 Extracting shared components...");
|
|
7718
|
+
try {
|
|
7719
|
+
const extraction = await extractSharedComponents(homePageCode, projectRoot, provider ?? "auto");
|
|
7720
|
+
parseOpts.sharedComponentsSummary = extraction.summary;
|
|
7721
|
+
if (extraction.components.length > 0) {
|
|
7722
|
+
const names = extraction.components.map((c) => c.name).join(", ");
|
|
7723
|
+
spinner.succeed(`Phase 3.5/5 \u2014 Extracted ${extraction.components.length} shared components (${names})`);
|
|
7724
|
+
} else {
|
|
7725
|
+
spinner.succeed("Phase 3.5/5 \u2014 No shared components extracted");
|
|
7726
|
+
}
|
|
7727
|
+
} catch {
|
|
7728
|
+
spinner.warn("Phase 3.5/5 \u2014 Could not extract shared components (continuing without)");
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7697
7731
|
}
|
|
7698
7732
|
if (remainingPages.length === 0) {
|
|
7699
7733
|
return homeRequest ? [homeRequest] : [];
|
|
7700
7734
|
}
|
|
7701
|
-
spinner.start(`Phase 4/
|
|
7735
|
+
spinner.start(`Phase 4/5 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
7702
7736
|
const sharedNote = "Header and Footer are shared components rendered by the root layout. Do NOT include any site-wide <header>, <nav>, or <footer> in this page. Start with the main content directly.";
|
|
7703
7737
|
const routeNote = `EXISTING ROUTES in this project: ${allRoutes}. All internal links MUST point to one of these routes. If a target doesn't exist, use href="#".`;
|
|
7704
7738
|
const alignmentNote = 'CRITICAL LAYOUT RULE: Every <section> must wrap its content in a container div matching the header width. Use the EXACT same container classes as shown in the style context (e.g. className="container max-w-6xl px-4" or className="max-w-6xl mx-auto px-4"). Inner content can use narrower max-w for text centering, but the outer section container MUST match.';
|
|
@@ -7721,12 +7755,12 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7721
7755
|
try {
|
|
7722
7756
|
const result = await parseModification(prompt, modCtx, provider, parseOpts);
|
|
7723
7757
|
phase4Done++;
|
|
7724
|
-
spinner.text = `Phase 4/
|
|
7758
|
+
spinner.text = `Phase 4/5 \u2014 ${phase4Done}/${remainingPages.length} pages generated...`;
|
|
7725
7759
|
const codePage = result.requests.find((r) => r.type === "add-page");
|
|
7726
7760
|
return codePage || { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7727
7761
|
} catch {
|
|
7728
7762
|
phase4Done++;
|
|
7729
|
-
spinner.text = `Phase 4/
|
|
7763
|
+
spinner.text = `Phase 4/5 \u2014 ${phase4Done}/${remainingPages.length} pages generated...`;
|
|
7730
7764
|
return { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7731
7765
|
}
|
|
7732
7766
|
},
|
|
@@ -7757,9 +7791,75 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7757
7791
|
}
|
|
7758
7792
|
}
|
|
7759
7793
|
const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
|
|
7760
|
-
spinner.succeed(`Phase 4/
|
|
7794
|
+
spinner.succeed(`Phase 4/5 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
|
|
7761
7795
|
return allRequests;
|
|
7762
7796
|
}
|
|
7797
|
+
var SharedExtractionItemSchema = z.object({
|
|
7798
|
+
name: z.string().min(2).max(50),
|
|
7799
|
+
type: z.enum(["section", "widget"]),
|
|
7800
|
+
description: z.string().max(200).default(""),
|
|
7801
|
+
propsInterface: z.string().default("{}"),
|
|
7802
|
+
code: z.string()
|
|
7803
|
+
});
|
|
7804
|
+
var SharedExtractionResponseSchema = z.object({
|
|
7805
|
+
components: z.array(SharedExtractionItemSchema).max(5).default([])
|
|
7806
|
+
});
|
|
7807
|
+
async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
7808
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7809
|
+
let ai;
|
|
7810
|
+
try {
|
|
7811
|
+
ai = await createAIProvider(aiProvider);
|
|
7812
|
+
} catch {
|
|
7813
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7814
|
+
}
|
|
7815
|
+
if (!ai.extractSharedComponents) {
|
|
7816
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7817
|
+
}
|
|
7818
|
+
let rawItems;
|
|
7819
|
+
try {
|
|
7820
|
+
const reservedNames = getComponentProvider().listNames();
|
|
7821
|
+
const existingNames = manifest.shared.map((e) => e.name);
|
|
7822
|
+
const result = await ai.extractSharedComponents(homePageCode, reservedNames, existingNames);
|
|
7823
|
+
const parsed = SharedExtractionResponseSchema.safeParse(result);
|
|
7824
|
+
rawItems = parsed.success ? parsed.data.components : [];
|
|
7825
|
+
} catch {
|
|
7826
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7827
|
+
}
|
|
7828
|
+
const reservedSet = new Set(getComponentProvider().listNames().map((n) => n.toLowerCase()));
|
|
7829
|
+
const existingSet = new Set(manifest.shared.map((e) => e.name.toLowerCase()));
|
|
7830
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
7831
|
+
const filtered = rawItems.filter((item) => {
|
|
7832
|
+
if (item.code.split("\n").length < 10) return false;
|
|
7833
|
+
if (reservedSet.has(item.name.toLowerCase())) return false;
|
|
7834
|
+
if (existingSet.has(item.name.toLowerCase())) return false;
|
|
7835
|
+
if (seenNames.has(item.name.toLowerCase())) return false;
|
|
7836
|
+
seenNames.add(item.name.toLowerCase());
|
|
7837
|
+
return true;
|
|
7838
|
+
});
|
|
7839
|
+
const results = [];
|
|
7840
|
+
const provider = getComponentProvider();
|
|
7841
|
+
for (const item of filtered) {
|
|
7842
|
+
try {
|
|
7843
|
+
const { code: fixedCode } = await autoFixCode(item.code);
|
|
7844
|
+
const shadcnImports = [...fixedCode.matchAll(/from\s+["']@\/components\/ui\/(.+?)["']/g)];
|
|
7845
|
+
for (const match of shadcnImports) {
|
|
7846
|
+
await provider.installComponent(match[1], projectRoot);
|
|
7847
|
+
}
|
|
7848
|
+
const result = await generateSharedComponent2(projectRoot, {
|
|
7849
|
+
name: item.name,
|
|
7850
|
+
type: item.type,
|
|
7851
|
+
code: fixedCode,
|
|
7852
|
+
description: item.description,
|
|
7853
|
+
propsInterface: item.propsInterface,
|
|
7854
|
+
usedIn: []
|
|
7855
|
+
});
|
|
7856
|
+
results.push(result);
|
|
7857
|
+
} catch {
|
|
7858
|
+
}
|
|
7859
|
+
}
|
|
7860
|
+
const updatedManifest = await loadManifest5(projectRoot);
|
|
7861
|
+
return { components: results, summary: buildSharedComponentsSummary(updatedManifest) };
|
|
7862
|
+
}
|
|
7763
7863
|
function extractAppNameFromPrompt(prompt) {
|
|
7764
7864
|
const patterns = [
|
|
7765
7865
|
/(?:called|named|app\s+name)\s+["']([^"']+)["']/i,
|
|
@@ -7807,11 +7907,11 @@ import { dirname as dirname6 } from "path";
|
|
|
7807
7907
|
import chalk11 from "chalk";
|
|
7808
7908
|
import {
|
|
7809
7909
|
getTemplateForPageType,
|
|
7810
|
-
loadManifest as
|
|
7910
|
+
loadManifest as loadManifest6,
|
|
7811
7911
|
saveManifest,
|
|
7812
7912
|
updateUsedIn,
|
|
7813
7913
|
findSharedComponentByIdOrName,
|
|
7814
|
-
generateSharedComponent as
|
|
7914
|
+
generateSharedComponent as generateSharedComponent4
|
|
7815
7915
|
} from "@getcoherent/core";
|
|
7816
7916
|
|
|
7817
7917
|
// src/commands/chat/code-generator.ts
|
|
@@ -7824,7 +7924,7 @@ import {
|
|
|
7824
7924
|
PageGenerator,
|
|
7825
7925
|
TailwindConfigGenerator
|
|
7826
7926
|
} from "@getcoherent/core";
|
|
7827
|
-
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as
|
|
7927
|
+
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent3 } from "@getcoherent/core";
|
|
7828
7928
|
import chalk9 from "chalk";
|
|
7829
7929
|
|
|
7830
7930
|
// src/utils/file-hashes.ts
|
|
@@ -7950,7 +8050,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7950
8050
|
if (navType === "header" || navType === "both") {
|
|
7951
8051
|
if (await canOverwriteShared(projectRoot, "components/shared/header.tsx", hashes)) {
|
|
7952
8052
|
const headerCode = generator.generateSharedHeaderCode();
|
|
7953
|
-
await
|
|
8053
|
+
await generateSharedComponent3(projectRoot, {
|
|
7954
8054
|
name: "Header",
|
|
7955
8055
|
type: "layout",
|
|
7956
8056
|
code: headerCode,
|
|
@@ -7962,7 +8062,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7962
8062
|
}
|
|
7963
8063
|
if (await canOverwriteShared(projectRoot, "components/shared/footer.tsx", hashes)) {
|
|
7964
8064
|
const footerCode = generator.generateSharedFooterCode();
|
|
7965
|
-
await
|
|
8065
|
+
await generateSharedComponent3(projectRoot, {
|
|
7966
8066
|
name: "Footer",
|
|
7967
8067
|
type: "layout",
|
|
7968
8068
|
code: footerCode,
|
|
@@ -7974,7 +8074,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7974
8074
|
if (navType === "sidebar" || navType === "both") {
|
|
7975
8075
|
if (await canOverwriteShared(projectRoot, "components/shared/sidebar.tsx", hashes)) {
|
|
7976
8076
|
const sidebarCode = generator.generateSharedSidebarCode();
|
|
7977
|
-
await
|
|
8077
|
+
await generateSharedComponent3(projectRoot, {
|
|
7978
8078
|
name: "AppSidebar",
|
|
7979
8079
|
type: "layout",
|
|
7980
8080
|
code: sidebarCode,
|
|
@@ -8526,7 +8626,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8526
8626
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
8527
8627
|
}
|
|
8528
8628
|
await writeFile(pageFilePath, fixedCode);
|
|
8529
|
-
const manifest = await
|
|
8629
|
+
const manifest = await loadManifest6(projectRoot);
|
|
8530
8630
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
8531
8631
|
const routePath = route.replace(/^\//, "");
|
|
8532
8632
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
@@ -8596,7 +8696,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8596
8696
|
};
|
|
8597
8697
|
}
|
|
8598
8698
|
const extractedCode = await ai.extractBlockAsComponent(sourceCode, blockHint, componentName);
|
|
8599
|
-
const created = await
|
|
8699
|
+
const created = await generateSharedComponent4(projectRoot, {
|
|
8600
8700
|
name: componentName,
|
|
8601
8701
|
type: "section",
|
|
8602
8702
|
code: extractedCode,
|
|
@@ -8631,7 +8731,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8631
8731
|
await writeFile(fullPath, fixedCode);
|
|
8632
8732
|
usedInFiles.push(relPath);
|
|
8633
8733
|
}
|
|
8634
|
-
const manifest = await
|
|
8734
|
+
const manifest = await loadManifest6(projectRoot);
|
|
8635
8735
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
8636
8736
|
await saveManifest(projectRoot, nextManifest);
|
|
8637
8737
|
printPromoteAndLinkReport({
|
|
@@ -8820,7 +8920,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8820
8920
|
cm.updateConfig(cfg);
|
|
8821
8921
|
pm.updateConfig(cfg);
|
|
8822
8922
|
}
|
|
8823
|
-
const manifestForAudit = await
|
|
8923
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
8824
8924
|
await warnInlineDuplicates(projectRoot, page.name || page.id || route.slice(1), codeToWrite, manifestForAudit);
|
|
8825
8925
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
8826
8926
|
printPostGenerationReport({
|
|
@@ -9020,7 +9120,7 @@ ${pagesCtx}`
|
|
|
9020
9120
|
cm.updateConfig(cfg);
|
|
9021
9121
|
pm.updateConfig(cfg);
|
|
9022
9122
|
}
|
|
9023
|
-
const manifestForAudit = await
|
|
9123
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
9024
9124
|
await warnInlineDuplicates(
|
|
9025
9125
|
projectRoot,
|
|
9026
9126
|
pageDef.name || pageDef.id || route.slice(1),
|
|
@@ -9063,7 +9163,7 @@ ${pagesCtx}`
|
|
|
9063
9163
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
9064
9164
|
}
|
|
9065
9165
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9066
|
-
const manifest = await
|
|
9166
|
+
const manifest = await loadManifest6(projectRoot);
|
|
9067
9167
|
printPostGenerationReport({
|
|
9068
9168
|
action: "updated",
|
|
9069
9169
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -9180,7 +9280,7 @@ function hasNavChanged(before, after) {
|
|
|
9180
9280
|
import chalk12 from "chalk";
|
|
9181
9281
|
import { resolve as resolve8 } from "path";
|
|
9182
9282
|
import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
9183
|
-
import { DesignSystemManager as DesignSystemManager6, ComponentManager as
|
|
9283
|
+
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
9184
9284
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
9185
9285
|
async function interactiveChat(options, chatCommandFn) {
|
|
9186
9286
|
const { createInterface } = await import("readline");
|
|
@@ -9191,7 +9291,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9191
9291
|
const config2 = await loadConfig(configPath);
|
|
9192
9292
|
const dsm = new DesignSystemManager6(configPath);
|
|
9193
9293
|
await dsm.load();
|
|
9194
|
-
const cm = new
|
|
9294
|
+
const cm = new ComponentManager4(config2);
|
|
9195
9295
|
const validProviders = ["claude", "openai", "auto"];
|
|
9196
9296
|
const provider = (options.provider || "auto").toLowerCase();
|
|
9197
9297
|
if (!validProviders.includes(provider)) {
|
|
@@ -9250,7 +9350,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9250
9350
|
return;
|
|
9251
9351
|
}
|
|
9252
9352
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
9253
|
-
const manifest = await
|
|
9353
|
+
const manifest = await loadManifest7(projectRoot);
|
|
9254
9354
|
if (manifest.shared.length === 0) {
|
|
9255
9355
|
console.log(chalk12.gray("\n No shared components yet.\n"));
|
|
9256
9356
|
} else {
|
|
@@ -9284,7 +9384,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9284
9384
|
}
|
|
9285
9385
|
if (lower === "status") {
|
|
9286
9386
|
const currentConfig = dsm.getConfig();
|
|
9287
|
-
const manifest = await
|
|
9387
|
+
const manifest = await loadManifest7(projectRoot);
|
|
9288
9388
|
console.log(chalk12.bold(`
|
|
9289
9389
|
${currentConfig.name || "Coherent Project"}`));
|
|
9290
9390
|
console.log(
|
|
@@ -9417,7 +9517,7 @@ async function chatCommand(message, options) {
|
|
|
9417
9517
|
const storedHashes = await loadHashes(projectRoot);
|
|
9418
9518
|
const dsm = new DesignSystemManager7(configPath);
|
|
9419
9519
|
await dsm.load();
|
|
9420
|
-
const cm = new
|
|
9520
|
+
const cm = new ComponentManager5(config2);
|
|
9421
9521
|
const pm = new PageManager3(config2, cm);
|
|
9422
9522
|
spinner.succeed("Configuration loaded");
|
|
9423
9523
|
message = await resolveTargetFlags(message, options, config2, projectRoot);
|
|
@@ -9473,7 +9573,7 @@ async function chatCommand(message, options) {
|
|
|
9473
9573
|
}
|
|
9474
9574
|
}
|
|
9475
9575
|
spinner.start("Parsing your request...");
|
|
9476
|
-
let manifest = await
|
|
9576
|
+
let manifest = await loadManifest8(project.root);
|
|
9477
9577
|
const validShared = manifest.shared.filter((s) => {
|
|
9478
9578
|
const fp = resolve9(project.root, s.file);
|
|
9479
9579
|
return existsSync16(fp);
|
|
@@ -9486,12 +9586,7 @@ async function chatCommand(message, options) {
|
|
|
9486
9586
|
console.log(chalk13.dim(`[pre-gen] Cleaned ${cleaned} orphaned component(s) from manifest`));
|
|
9487
9587
|
}
|
|
9488
9588
|
}
|
|
9489
|
-
const sharedComponentsSummary = manifest
|
|
9490
|
-
const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
9491
|
-
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
9492
|
-
return ` ${e.id} ${e.name} (${e.type})${desc}
|
|
9493
|
-
Import: @/components/shared/${importPath}`;
|
|
9494
|
-
}).join("\n") : void 0;
|
|
9589
|
+
const sharedComponentsSummary = buildSharedComponentsSummary(manifest);
|
|
9495
9590
|
if (DEBUG4 && sharedComponentsSummary) {
|
|
9496
9591
|
console.log(chalk13.dim("[add-page] sharedComponentsSummary in prompt:\n" + sharedComponentsSummary));
|
|
9497
9592
|
}
|
|
@@ -10131,7 +10226,7 @@ import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as Comp
|
|
|
10131
10226
|
// src/utils/file-watcher.ts
|
|
10132
10227
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
10133
10228
|
import { relative as relative4, join as join13 } from "path";
|
|
10134
|
-
import { loadManifest as
|
|
10229
|
+
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
10135
10230
|
|
|
10136
10231
|
// src/utils/component-integrity.ts
|
|
10137
10232
|
import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync3 } from "fs";
|
|
@@ -10485,7 +10580,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10485
10580
|
if (config2.warnSharedReuse) {
|
|
10486
10581
|
let manifest;
|
|
10487
10582
|
try {
|
|
10488
|
-
manifest = await
|
|
10583
|
+
manifest = await loadManifest9(projectRoot);
|
|
10489
10584
|
} catch {
|
|
10490
10585
|
manifest = { shared: [], nextId: 1 };
|
|
10491
10586
|
}
|
|
@@ -10504,7 +10599,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10504
10599
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10505
10600
|
try {
|
|
10506
10601
|
const chalk33 = (await import("chalk")).default;
|
|
10507
|
-
const manifest = await
|
|
10602
|
+
const manifest = await loadManifest9(projectRoot);
|
|
10508
10603
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
10509
10604
|
if (orphaned) {
|
|
10510
10605
|
const cleaned = {
|
|
@@ -10525,7 +10620,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10525
10620
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
10526
10621
|
try {
|
|
10527
10622
|
const chalk33 = (await import("chalk")).default;
|
|
10528
|
-
const manifest = await
|
|
10623
|
+
const manifest = await loadManifest9(projectRoot);
|
|
10529
10624
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10530
10625
|
if (alreadyRegistered) return;
|
|
10531
10626
|
const code = readFileSync12(filePath, "utf-8");
|
|
@@ -11449,10 +11544,10 @@ import { readdirSync as readdirSync5, readFileSync as readFileSync14, existsSync
|
|
|
11449
11544
|
import { resolve as resolve12, join as join16 } from "path";
|
|
11450
11545
|
import {
|
|
11451
11546
|
DesignSystemManager as DesignSystemManager11,
|
|
11452
|
-
ComponentManager as
|
|
11547
|
+
ComponentManager as ComponentManager6,
|
|
11453
11548
|
PageManager as PageManager4,
|
|
11454
11549
|
ComponentGenerator as ComponentGenerator4,
|
|
11455
|
-
loadManifest as
|
|
11550
|
+
loadManifest as loadManifest10,
|
|
11456
11551
|
saveManifest as saveManifest4
|
|
11457
11552
|
} from "@getcoherent/core";
|
|
11458
11553
|
function extractComponentIdsFromCode2(code) {
|
|
@@ -11539,7 +11634,7 @@ async function fixCommand(opts = {}) {
|
|
|
11539
11634
|
dsm = new DesignSystemManager11(project.configPath);
|
|
11540
11635
|
await dsm.load();
|
|
11541
11636
|
const config2 = dsm.getConfig();
|
|
11542
|
-
cm = new
|
|
11637
|
+
cm = new ComponentManager6(config2);
|
|
11543
11638
|
pm = new PageManager4(config2, cm);
|
|
11544
11639
|
const missingComponents = [];
|
|
11545
11640
|
const missingFiles = [];
|
|
@@ -11662,7 +11757,7 @@ async function fixCommand(opts = {}) {
|
|
|
11662
11757
|
fileIssues.push({ path: relativePath, report });
|
|
11663
11758
|
}
|
|
11664
11759
|
try {
|
|
11665
|
-
let manifest = await
|
|
11760
|
+
let manifest = await loadManifest10(project.root);
|
|
11666
11761
|
let manifestModified = false;
|
|
11667
11762
|
const { manifest: cleaned, removed: orphaned } = removeOrphanedEntries(project.root, manifest);
|
|
11668
11763
|
if (orphaned.length > 0) {
|
|
@@ -11762,7 +11857,7 @@ async function fixCommand(opts = {}) {
|
|
|
11762
11857
|
import chalk19 from "chalk";
|
|
11763
11858
|
import { resolve as resolve13 } from "path";
|
|
11764
11859
|
import { readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
11765
|
-
import { loadManifest as
|
|
11860
|
+
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11766
11861
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11767
11862
|
function findTsxFiles(dir) {
|
|
11768
11863
|
const results = [];
|
|
@@ -11889,7 +11984,7 @@ async function checkCommand(opts = {}) {
|
|
|
11889
11984
|
\u{1F517} Internal Links`) + chalk19.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
11890
11985
|
}
|
|
11891
11986
|
try {
|
|
11892
|
-
const manifest = await
|
|
11987
|
+
const manifest = await loadManifest11(project.root);
|
|
11893
11988
|
if (manifest.shared.length > 0) {
|
|
11894
11989
|
for (const entry of manifest.shared) {
|
|
11895
11990
|
const fullPath = resolve13(project.root, entry.file);
|
|
@@ -11905,7 +12000,7 @@ async function checkCommand(opts = {}) {
|
|
|
11905
12000
|
}
|
|
11906
12001
|
if (!skipShared) {
|
|
11907
12002
|
try {
|
|
11908
|
-
const manifest = await
|
|
12003
|
+
const manifest = await loadManifest11(projectRoot);
|
|
11909
12004
|
if (!opts.json && manifest.shared.length > 0) {
|
|
11910
12005
|
console.log(chalk19.cyan(`
|
|
11911
12006
|
\u{1F9E9} Shared Components`) + chalk19.dim(` (${manifest.shared.length} registered)
|
|
@@ -12086,9 +12181,9 @@ import { Command } from "commander";
|
|
|
12086
12181
|
import chalk25 from "chalk";
|
|
12087
12182
|
import {
|
|
12088
12183
|
DesignSystemManager as DesignSystemManager12,
|
|
12089
|
-
ComponentManager as
|
|
12090
|
-
loadManifest as
|
|
12091
|
-
generateSharedComponent as
|
|
12184
|
+
ComponentManager as ComponentManager7,
|
|
12185
|
+
loadManifest as loadManifest12,
|
|
12186
|
+
generateSharedComponent as generateSharedComponent5,
|
|
12092
12187
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
12093
12188
|
} from "@getcoherent/core";
|
|
12094
12189
|
import { existsSync as existsSync23 } from "fs";
|
|
@@ -12127,8 +12222,8 @@ function createComponentsCommand() {
|
|
|
12127
12222
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
12128
12223
|
await dsm.load();
|
|
12129
12224
|
const config2 = dsm.getConfig();
|
|
12130
|
-
const cm = new
|
|
12131
|
-
const manifest = await
|
|
12225
|
+
const cm = new ComponentManager7(config2);
|
|
12226
|
+
const manifest = await loadManifest12(project.root);
|
|
12132
12227
|
if (opts.json) {
|
|
12133
12228
|
const installed2 = cm.getAllComponents();
|
|
12134
12229
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
@@ -12180,7 +12275,7 @@ function createComponentsCommand() {
|
|
|
12180
12275
|
sharedCmd.option("--json", "Machine-readable JSON output").option("--verbose", "Show file paths and usage details").action(async (opts) => {
|
|
12181
12276
|
const project = findConfig();
|
|
12182
12277
|
if (!project) exitNotCoherent();
|
|
12183
|
-
const manifest = await
|
|
12278
|
+
const manifest = await loadManifest12(project.root);
|
|
12184
12279
|
if (opts.json) {
|
|
12185
12280
|
console.log(JSON.stringify(manifest, null, 2));
|
|
12186
12281
|
return;
|
|
@@ -12209,7 +12304,7 @@ function createComponentsCommand() {
|
|
|
12209
12304
|
const project = findConfig();
|
|
12210
12305
|
if (!project) exitNotCoherent();
|
|
12211
12306
|
const type = opts.type === "section" || opts.type === "widget" ? opts.type : "layout";
|
|
12212
|
-
const result = await
|
|
12307
|
+
const result = await generateSharedComponent5(project.root, {
|
|
12213
12308
|
name: name.trim(),
|
|
12214
12309
|
type,
|
|
12215
12310
|
description: opts.description,
|
|
@@ -12260,7 +12355,7 @@ import {
|
|
|
12260
12355
|
EXAMPLE_MULTIPAGE_CONFIG,
|
|
12261
12356
|
normalizeFigmaComponents,
|
|
12262
12357
|
setSharedMapping,
|
|
12263
|
-
generateSharedComponent as
|
|
12358
|
+
generateSharedComponent as generateSharedComponent6,
|
|
12264
12359
|
generatePagesFromFigma,
|
|
12265
12360
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout4,
|
|
12266
12361
|
DesignSystemManager as DesignSystemManager13,
|
|
@@ -12457,7 +12552,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
12457
12552
|
else {
|
|
12458
12553
|
stats.sharedCount++;
|
|
12459
12554
|
if (!dryRun) {
|
|
12460
|
-
const { id, name, file } = await
|
|
12555
|
+
const { id, name, file } = await generateSharedComponent6(projectRoot, {
|
|
12461
12556
|
name: entry.suggestedName,
|
|
12462
12557
|
type: "widget",
|
|
12463
12558
|
code: entry.suggestedTsx,
|
|
@@ -12884,7 +12979,7 @@ import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
|
|
|
12884
12979
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
12885
12980
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12886
12981
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
12887
|
-
import { loadManifest as
|
|
12982
|
+
import { loadManifest as loadManifest13, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
|
|
12888
12983
|
function extractTokensFromProject(projectRoot) {
|
|
12889
12984
|
const lightColors = {};
|
|
12890
12985
|
const darkColors = {};
|
|
@@ -13164,7 +13259,7 @@ async function syncCommand(options = {}) {
|
|
|
13164
13259
|
let reconcileResult = null;
|
|
13165
13260
|
if (doComponents) {
|
|
13166
13261
|
spinner.start("Reconciling shared components...");
|
|
13167
|
-
const manifest = await
|
|
13262
|
+
const manifest = await loadManifest13(project.root);
|
|
13168
13263
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
13169
13264
|
reconcileResult = rr;
|
|
13170
13265
|
if (!dryRun) {
|
|
@@ -316,6 +316,54 @@ Tasks:
|
|
|
316
316
|
if (!content) throw new Error("Empty response from OpenAI");
|
|
317
317
|
return content.trim().replace(/^```(?:tsx?|jsx?)\s*/i, "").replace(/\s*```$/i, "");
|
|
318
318
|
}
|
|
319
|
+
async extractSharedComponents(pageCode, reservedNames, existingSharedNames) {
|
|
320
|
+
try {
|
|
321
|
+
const response = await this.client.chat.completions.create({
|
|
322
|
+
model: this.defaultModel,
|
|
323
|
+
messages: [
|
|
324
|
+
{
|
|
325
|
+
role: "system",
|
|
326
|
+
content: "You are a React/Next.js component extraction specialist. Analyze page code and identify reusable UI patterns that can be extracted into shared components. Return valid JSON only."
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
role: "user",
|
|
330
|
+
content: `Analyze this page and extract reusable components.
|
|
331
|
+
|
|
332
|
+
PAGE CODE:
|
|
333
|
+
${pageCode}
|
|
334
|
+
|
|
335
|
+
Rules:
|
|
336
|
+
- Extract 1-5 components maximum
|
|
337
|
+
- Each component must be \u226510 lines of meaningful JSX
|
|
338
|
+
- Output complete, self-contained TypeScript modules with:
|
|
339
|
+
- "use client" directive (if hooks or event handlers are used)
|
|
340
|
+
- All necessary imports (shadcn/ui from @/components/ui/*, lucide-react, next/link, etc.)
|
|
341
|
+
- A typed props interface exported as a named type
|
|
342
|
+
- A named export function (not default export)
|
|
343
|
+
- Do NOT extract: the entire page, trivial wrappers, layout components (header, footer, nav)
|
|
344
|
+
- Do NOT use these names (reserved for shadcn/ui): ${reservedNames.join(", ")}
|
|
345
|
+
- Do NOT use these names (already shared): ${existingSharedNames.join(", ")}
|
|
346
|
+
- Look for: cards with icon+title+description, pricing tiers, testimonial blocks, stat displays, CTA sections
|
|
347
|
+
|
|
348
|
+
Each component object: "name" (PascalCase), "type" ("section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
349
|
+
|
|
350
|
+
If no repeating patterns found: { "components": [] }`
|
|
351
|
+
}
|
|
352
|
+
],
|
|
353
|
+
response_format: { type: "json_object" },
|
|
354
|
+
temperature: 0.3,
|
|
355
|
+
max_tokens: 16384
|
|
356
|
+
});
|
|
357
|
+
const content = response.choices[0]?.message?.content;
|
|
358
|
+
if (!content) return { components: [] };
|
|
359
|
+
const jsonText = this.extractJSON(content);
|
|
360
|
+
const parsed = JSON.parse(jsonText);
|
|
361
|
+
const components = Array.isArray(parsed.components) ? parsed.components : [];
|
|
362
|
+
return { components };
|
|
363
|
+
} catch {
|
|
364
|
+
return { components: [] };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
319
367
|
async extractBlockAsComponent(pageCode, blockHint, componentName) {
|
|
320
368
|
const response = await this.client.chat.completions.create({
|
|
321
369
|
model: this.defaultModel,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.5.
|
|
6
|
+
"version": "0.5.14",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -33,15 +33,8 @@
|
|
|
33
33
|
],
|
|
34
34
|
"author": "Coherent Design Method",
|
|
35
35
|
"license": "MIT",
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "tsup --watch",
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "vitest"
|
|
41
|
-
},
|
|
42
36
|
"dependencies": {
|
|
43
37
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
44
|
-
"@getcoherent/core": "workspace:*",
|
|
45
38
|
"chalk": "^5.3.0",
|
|
46
39
|
"chokidar": "^4.0.1",
|
|
47
40
|
"commander": "^11.1.0",
|
|
@@ -49,12 +42,19 @@
|
|
|
49
42
|
"open": "^10.1.0",
|
|
50
43
|
"ora": "^7.0.1",
|
|
51
44
|
"prompts": "^2.4.2",
|
|
52
|
-
"zod": "^3.22.4"
|
|
45
|
+
"zod": "^3.22.4",
|
|
46
|
+
"@getcoherent/core": "0.5.14"
|
|
53
47
|
},
|
|
54
48
|
"devDependencies": {
|
|
55
49
|
"@types/node": "^20.11.0",
|
|
56
50
|
"@types/prompts": "^2.4.9",
|
|
57
51
|
"tsup": "^8.0.1",
|
|
58
52
|
"typescript": "^5.3.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|