@getcoherent/cli 0.6.1 → 0.6.3
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.
|
|
@@ -3,29 +3,83 @@ import { z } from "zod";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { dirname, resolve } from "path";
|
|
5
5
|
import { mkdir, writeFile } from "fs/promises";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
var LAYOUT_SYNONYMS = {
|
|
8
|
+
horizontal: "header",
|
|
9
|
+
top: "header",
|
|
10
|
+
nav: "header",
|
|
11
|
+
navbar: "header",
|
|
12
|
+
topbar: "header",
|
|
13
|
+
"top-bar": "header",
|
|
14
|
+
vertical: "sidebar",
|
|
15
|
+
left: "sidebar",
|
|
16
|
+
side: "sidebar",
|
|
17
|
+
drawer: "sidebar",
|
|
18
|
+
full: "both",
|
|
19
|
+
combined: "both",
|
|
20
|
+
empty: "none",
|
|
21
|
+
minimal: "none",
|
|
22
|
+
clean: "none"
|
|
23
|
+
};
|
|
24
|
+
var PAGE_TYPE_SYNONYMS = {
|
|
25
|
+
landing: "marketing",
|
|
26
|
+
public: "marketing",
|
|
27
|
+
home: "marketing",
|
|
28
|
+
website: "marketing",
|
|
29
|
+
static: "marketing",
|
|
30
|
+
application: "app",
|
|
31
|
+
dashboard: "app",
|
|
32
|
+
admin: "app",
|
|
33
|
+
panel: "app",
|
|
34
|
+
console: "app",
|
|
35
|
+
authentication: "auth",
|
|
36
|
+
login: "auth",
|
|
37
|
+
"log-in": "auth",
|
|
38
|
+
register: "auth",
|
|
39
|
+
signin: "auth",
|
|
40
|
+
"sign-in": "auth",
|
|
41
|
+
signup: "auth",
|
|
42
|
+
"sign-up": "auth"
|
|
43
|
+
};
|
|
44
|
+
var COMPONENT_TYPE_SYNONYMS = {
|
|
45
|
+
component: "widget",
|
|
46
|
+
ui: "widget",
|
|
47
|
+
element: "widget",
|
|
48
|
+
block: "widget",
|
|
49
|
+
"page-section": "section",
|
|
50
|
+
hero: "section",
|
|
51
|
+
feature: "section",
|
|
52
|
+
area: "section"
|
|
53
|
+
};
|
|
54
|
+
function normalizeEnum(synonyms) {
|
|
55
|
+
return (v) => {
|
|
56
|
+
const trimmed = v.trim().toLowerCase();
|
|
57
|
+
return synonyms[trimmed] ?? trimmed;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
6
60
|
var RouteGroupSchema = z.object({
|
|
7
61
|
id: z.string(),
|
|
8
|
-
layout: z.enum(["header", "sidebar", "both", "none"]),
|
|
62
|
+
layout: z.string().transform(normalizeEnum(LAYOUT_SYNONYMS)).pipe(z.enum(["header", "sidebar", "both", "none"])),
|
|
9
63
|
pages: z.array(z.string())
|
|
10
64
|
});
|
|
11
65
|
var PlannedComponentSchema = z.object({
|
|
12
66
|
name: z.string(),
|
|
13
|
-
description: z.string(),
|
|
14
|
-
props: z.string(),
|
|
15
|
-
usedBy: z.array(z.string()),
|
|
16
|
-
type: z.enum(["section", "widget"]),
|
|
67
|
+
description: z.string().default(""),
|
|
68
|
+
props: z.string().default("{}"),
|
|
69
|
+
usedBy: z.array(z.string()).default([]),
|
|
70
|
+
type: z.string().transform(normalizeEnum(COMPONENT_TYPE_SYNONYMS)).pipe(z.enum(["section", "widget"])),
|
|
17
71
|
shadcnDeps: z.array(z.string()).default([])
|
|
18
72
|
});
|
|
19
73
|
var PageNoteSchema = z.object({
|
|
20
|
-
type: z.enum(["marketing", "app", "auth"]),
|
|
21
|
-
sections: z.array(z.string()),
|
|
74
|
+
type: z.string().transform(normalizeEnum(PAGE_TYPE_SYNONYMS)).pipe(z.enum(["marketing", "app", "auth"])),
|
|
75
|
+
sections: z.array(z.string()).default([]),
|
|
22
76
|
links: z.record(z.string()).optional()
|
|
23
77
|
});
|
|
24
78
|
var ArchitecturePlanSchema = z.object({
|
|
25
79
|
appName: z.string().optional(),
|
|
26
80
|
groups: z.array(RouteGroupSchema),
|
|
27
|
-
sharedComponents: z.array(PlannedComponentSchema).max(8),
|
|
28
|
-
pageNotes: z.record(z.string(), PageNoteSchema)
|
|
81
|
+
sharedComponents: z.array(PlannedComponentSchema).max(8).default([]),
|
|
82
|
+
pageNotes: z.record(z.string(), PageNoteSchema).default({})
|
|
29
83
|
});
|
|
30
84
|
function routeToKey(route) {
|
|
31
85
|
return route.replace(/^\//, "") || "home";
|
|
@@ -59,16 +113,21 @@ async function generateArchitecturePlan(pages, userMessage, aiProvider, layoutHi
|
|
|
59
113
|
User's request: "${userMessage}"
|
|
60
114
|
|
|
61
115
|
Navigation type requested: ${layoutHint || "auto-detect"}`;
|
|
116
|
+
const warnings = [];
|
|
62
117
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
63
118
|
try {
|
|
64
119
|
const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
|
|
65
120
|
const parsed = ArchitecturePlanSchema.safeParse(raw);
|
|
66
|
-
if (parsed.success) return parsed.data;
|
|
67
|
-
|
|
68
|
-
|
|
121
|
+
if (parsed.success) return { plan: parsed.data, warnings };
|
|
122
|
+
warnings.push(
|
|
123
|
+
`Validation (attempt ${attempt + 1}): ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
124
|
+
);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
warnings.push(`Error (attempt ${attempt + 1}): ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
+
if (attempt === 1) return { plan: null, warnings };
|
|
69
128
|
}
|
|
70
129
|
}
|
|
71
|
-
return null;
|
|
130
|
+
return { plan: null, warnings };
|
|
72
131
|
}
|
|
73
132
|
async function updateArchitecturePlan(existingPlan, newPages, userMessage, aiProvider) {
|
|
74
133
|
const userPrompt = `Existing plan:
|
|
@@ -83,7 +142,10 @@ Update the existing plan to include these new pages. Keep all existing groups, c
|
|
|
83
142
|
const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
|
|
84
143
|
const parsed = ArchitecturePlanSchema.safeParse(raw);
|
|
85
144
|
if (parsed.success) return parsed.data;
|
|
86
|
-
|
|
145
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
146
|
+
console.warn(chalk.dim(` Plan update validation failed: ${issues}`));
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.warn(chalk.dim(` Plan update error: ${err instanceof Error ? err.message : String(err)}`));
|
|
87
149
|
}
|
|
88
150
|
const merged = structuredClone(existingPlan);
|
|
89
151
|
const largestGroup = merged.groups.reduce(
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadPlan,
|
|
6
6
|
routeToKey,
|
|
7
7
|
savePlan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IKHAW6OI.js";
|
|
9
9
|
import {
|
|
10
10
|
__require
|
|
11
11
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -7191,7 +7191,8 @@ async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, mani
|
|
|
7191
7191
|
for (const t of sharedTokens) {
|
|
7192
7192
|
if (pageTokens.includes(t)) overlap++;
|
|
7193
7193
|
}
|
|
7194
|
-
|
|
7194
|
+
const overlapRatio = sharedTokens.size > 0 ? overlap / sharedTokens.size : 0;
|
|
7195
|
+
if (overlap >= 20 && overlapRatio >= 0.6) {
|
|
7195
7196
|
console.log(
|
|
7196
7197
|
chalk8.yellow(
|
|
7197
7198
|
`
|
|
@@ -7963,7 +7964,13 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7963
7964
|
try {
|
|
7964
7965
|
const ai = await createAIProvider(provider ?? "auto");
|
|
7965
7966
|
const layoutHint = modCtx.config.navigation?.type || null;
|
|
7966
|
-
plan = await generateArchitecturePlan(
|
|
7967
|
+
const { plan: generatedPlan, warnings: planWarnings } = await generateArchitecturePlan(
|
|
7968
|
+
pageNames,
|
|
7969
|
+
message,
|
|
7970
|
+
ai,
|
|
7971
|
+
layoutHint
|
|
7972
|
+
);
|
|
7973
|
+
plan = generatedPlan;
|
|
7967
7974
|
if (plan) {
|
|
7968
7975
|
const groupsSummary = plan.groups.map((g) => `${g.id} (${g.layout}, ${g.pages.length} pages)`).join(", ");
|
|
7969
7976
|
const sharedSummary = plan.sharedComponents.length > 0 ? plan.sharedComponents.map((c) => `${c.name} \u2192 ${c.usedBy.join(", ")}`).join(" | ") : "";
|
|
@@ -7987,6 +7994,9 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7987
7994
|
} else {
|
|
7988
7995
|
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7989
7996
|
}
|
|
7997
|
+
for (const w of planWarnings) {
|
|
7998
|
+
console.log(chalk9.dim(` ${w}`));
|
|
7999
|
+
}
|
|
7990
8000
|
} catch {
|
|
7991
8001
|
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7992
8002
|
}
|
|
@@ -8046,7 +8056,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
8046
8056
|
if (plan && plan.sharedComponents.length > 0) {
|
|
8047
8057
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
8048
8058
|
try {
|
|
8049
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
8059
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-R72I6RNM.js");
|
|
8050
8060
|
const generated = await generateSharedComponentsFromPlan(
|
|
8051
8061
|
plan,
|
|
8052
8062
|
styleContext,
|
|
@@ -10456,8 +10466,16 @@ async function chatCommand(message, options) {
|
|
|
10456
10466
|
if (authRelated) authRelated.forEach((l) => allLinkedRoutes.add(l));
|
|
10457
10467
|
}
|
|
10458
10468
|
const existingRoutes = new Set(currentConfig.pages.map((p) => p.route).filter(Boolean));
|
|
10469
|
+
const expandedExisting = new Set(existingRoutes);
|
|
10470
|
+
for (const route of existingRoutes) {
|
|
10471
|
+
const canonical = AUTH_SYNONYMS[route] ?? route;
|
|
10472
|
+
expandedExisting.add(canonical);
|
|
10473
|
+
for (const [syn, can] of Object.entries(AUTH_SYNONYMS)) {
|
|
10474
|
+
if (can === canonical) expandedExisting.add(syn);
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10459
10477
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
10460
|
-
if (
|
|
10478
|
+
if (expandedExisting.has(route)) return false;
|
|
10461
10479
|
if (existsSync16(routeToFsPath(projectRoot, route, false))) return false;
|
|
10462
10480
|
if (existsSync16(routeToFsPath(projectRoot, route, true))) return false;
|
|
10463
10481
|
return true;
|
|
@@ -10670,8 +10688,9 @@ async function chatCommand(message, options) {
|
|
|
10670
10688
|
const preflightNames = preflightInstalledIds.map((id) => updatedConfig.components.find((c) => c.id === id)?.name).filter(Boolean);
|
|
10671
10689
|
showPreview(normalizedRequests, results, updatedConfig, preflightNames);
|
|
10672
10690
|
if (scaffoldedPages.length > 0) {
|
|
10691
|
+
const uniqueScaffolded = [...new Map(scaffoldedPages.map((s) => [s.route, s])).values()];
|
|
10673
10692
|
console.log(chalk14.cyan("\u{1F517} Auto-scaffolded linked pages:"));
|
|
10674
|
-
|
|
10693
|
+
uniqueScaffolded.forEach(({ route, name }) => {
|
|
10675
10694
|
console.log(chalk14.white(` \u2728 ${name} \u2192 ${route}`));
|
|
10676
10695
|
});
|
|
10677
10696
|
console.log("");
|
|
@@ -11604,7 +11623,7 @@ async function previewCommand() {
|
|
|
11604
11623
|
}
|
|
11605
11624
|
console.log(chalk15.green("\n\u2705 Dependencies installed\n"));
|
|
11606
11625
|
} else {
|
|
11607
|
-
spinner.succeed("
|
|
11626
|
+
spinner.succeed("Dependencies installed");
|
|
11608
11627
|
}
|
|
11609
11628
|
if (needsGlobalsFix(projectRoot)) {
|
|
11610
11629
|
spinner.text = "Fixing globals.css...";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.6.
|
|
6
|
+
"version": "0.6.3",
|
|
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.6.3"
|
|
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
|
+
}
|