ui-pro-forge-cli 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/assets/data/app-interface.csv +31 -0
- package/assets/data/charts.csv +26 -0
- package/assets/data/colors.csv +194 -0
- package/assets/data/google-fonts.csv +1924 -0
- package/assets/data/icons.csv +106 -0
- package/assets/data/landing.csv +35 -0
- package/assets/data/motion.csv +17 -0
- package/assets/data/products.csv +193 -0
- package/assets/data/react-performance.csv +45 -0
- package/assets/data/stacks/angular.csv +51 -0
- package/assets/data/stacks/astro.csv +54 -0
- package/assets/data/stacks/avalonia.csv +57 -0
- package/assets/data/stacks/flutter.csv +53 -0
- package/assets/data/stacks/html-tailwind.csv +56 -0
- package/assets/data/stacks/javafx.csv +76 -0
- package/assets/data/stacks/jetpack-compose.csv +53 -0
- package/assets/data/stacks/laravel.csv +51 -0
- package/assets/data/stacks/nextjs.csv +53 -0
- package/assets/data/stacks/nuxt-ui.csv +71 -0
- package/assets/data/stacks/nuxtjs.csv +59 -0
- package/assets/data/stacks/react-native.csv +52 -0
- package/assets/data/stacks/react.csv +54 -0
- package/assets/data/stacks/shadcn.csv +61 -0
- package/assets/data/stacks/solidjs.csv +21 -0
- package/assets/data/stacks/svelte.csv +54 -0
- package/assets/data/stacks/swiftui.csv +51 -0
- package/assets/data/stacks/threejs.csv +54 -0
- package/assets/data/stacks/uno.csv +60 -0
- package/assets/data/stacks/vue.csv +50 -0
- package/assets/data/stacks/winui.csv +60 -0
- package/assets/data/styles.csv +86 -0
- package/assets/data/typography.csv +75 -0
- package/assets/data/ui-reasoning.csv +162 -0
- package/assets/data/ux-guidelines.csv +100 -0
- package/assets/scripts/core.py +463 -0
- package/assets/scripts/design_system.py +1371 -0
- package/assets/scripts/search.py +162 -0
- package/assets/scripts/tests/test_core.py +134 -0
- package/assets/scripts/validate_data.py +114 -0
- package/assets/skills/banner-design/SKILL.md +196 -0
- package/assets/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/brand/SKILL.md +97 -0
- package/assets/skills/brand/references/approval-checklist.md +169 -0
- package/assets/skills/brand/references/asset-organization.md +157 -0
- package/assets/skills/brand/references/brand-guideline-template.md +140 -0
- package/assets/skills/brand/references/color-palette-management.md +186 -0
- package/assets/skills/brand/references/consistency-checklist.md +94 -0
- package/assets/skills/brand/references/logo-usage-rules.md +185 -0
- package/assets/skills/brand/references/messaging-framework.md +85 -0
- package/assets/skills/brand/references/typography-specifications.md +214 -0
- package/assets/skills/brand/references/update.md +118 -0
- package/assets/skills/brand/references/visual-identity.md +96 -0
- package/assets/skills/brand/references/voice-framework.md +88 -0
- package/assets/skills/brand/scripts/extract-colors.cjs +341 -0
- package/assets/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/assets/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
- package/assets/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
- package/assets/skills/brand/scripts/validate-asset.cjs +387 -0
- package/assets/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/assets/skills/design/SKILL.md +313 -0
- package/assets/skills/design/data/cip/deliverables.csv +51 -0
- package/assets/skills/design/data/cip/industries.csv +21 -0
- package/assets/skills/design/data/cip/mockup-contexts.csv +21 -0
- package/assets/skills/design/data/cip/styles.csv +21 -0
- package/assets/skills/design/data/icon/styles.csv +16 -0
- package/assets/skills/design/data/logo/colors.csv +56 -0
- package/assets/skills/design/data/logo/industries.csv +56 -0
- package/assets/skills/design/data/logo/styles.csv +56 -0
- package/assets/skills/design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/design/references/cip-deliverable-guide.md +95 -0
- package/assets/skills/design/references/cip-design.md +121 -0
- package/assets/skills/design/references/cip-prompt-engineering.md +84 -0
- package/assets/skills/design/references/cip-style-guide.md +68 -0
- package/assets/skills/design/references/design-routing.md +207 -0
- package/assets/skills/design/references/icon-design.md +122 -0
- package/assets/skills/design/references/logo-color-psychology.md +101 -0
- package/assets/skills/design/references/logo-design.md +92 -0
- package/assets/skills/design/references/logo-prompt-engineering.md +158 -0
- package/assets/skills/design/references/logo-style-guide.md +109 -0
- package/assets/skills/design/references/slides-copywriting-formulas.md +84 -0
- package/assets/skills/design/references/slides-create.md +4 -0
- package/assets/skills/design/references/slides-html-template.md +295 -0
- package/assets/skills/design/references/slides-layout-patterns.md +137 -0
- package/assets/skills/design/references/slides-strategies.md +94 -0
- package/assets/skills/design/references/slides.md +42 -0
- package/assets/skills/design/references/social-photos-design.md +329 -0
- package/assets/skills/design/scripts/cip/core.py +215 -0
- package/assets/skills/design/scripts/cip/generate.py +484 -0
- package/assets/skills/design/scripts/cip/render-html.py +424 -0
- package/assets/skills/design/scripts/cip/search.py +127 -0
- package/assets/skills/design/scripts/icon/generate.py +487 -0
- package/assets/skills/design/scripts/logo/core.py +175 -0
- package/assets/skills/design/scripts/logo/generate.py +362 -0
- package/assets/skills/design/scripts/logo/search.py +114 -0
- package/assets/skills/design-system/SKILL.md +244 -0
- package/assets/skills/design-system/data/slide-backgrounds.csv +11 -0
- package/assets/skills/design-system/data/slide-charts.csv +26 -0
- package/assets/skills/design-system/data/slide-color-logic.csv +14 -0
- package/assets/skills/design-system/data/slide-copy.csv +26 -0
- package/assets/skills/design-system/data/slide-layout-logic.csv +16 -0
- package/assets/skills/design-system/data/slide-layouts.csv +26 -0
- package/assets/skills/design-system/data/slide-strategies.csv +16 -0
- package/assets/skills/design-system/data/slide-typography.csv +15 -0
- package/assets/skills/design-system/references/component-specs.md +236 -0
- package/assets/skills/design-system/references/component-tokens.md +214 -0
- package/assets/skills/design-system/references/primitive-tokens.md +203 -0
- package/assets/skills/design-system/references/semantic-tokens.md +215 -0
- package/assets/skills/design-system/references/states-and-variants.md +241 -0
- package/assets/skills/design-system/references/tailwind-integration.md +251 -0
- package/assets/skills/design-system/references/token-architecture.md +224 -0
- package/assets/skills/design-system/scripts/embed-tokens.cjs +99 -0
- package/assets/skills/design-system/scripts/fetch-background.py +317 -0
- package/assets/skills/design-system/scripts/generate-slide.py +770 -0
- package/assets/skills/design-system/scripts/generate-tokens.cjs +205 -0
- package/assets/skills/design-system/scripts/html-token-validator.py +327 -0
- package/assets/skills/design-system/scripts/search-slides.py +218 -0
- package/assets/skills/design-system/scripts/slide-token-validator.py +35 -0
- package/assets/skills/design-system/scripts/slide_search_core.py +453 -0
- package/assets/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
- package/assets/skills/design-system/scripts/validate-tokens.cjs +246 -0
- package/assets/skills/design-system/templates/design-tokens-starter.json +143 -0
- package/assets/skills/slides/SKILL.md +40 -0
- package/assets/skills/slides/references/copywriting-formulas.md +84 -0
- package/assets/skills/slides/references/create.md +4 -0
- package/assets/skills/slides/references/html-template.md +295 -0
- package/assets/skills/slides/references/layout-patterns.md +137 -0
- package/assets/skills/slides/references/slide-strategies.md +94 -0
- package/assets/skills/ui-styling/LICENSE.txt +202 -0
- package/assets/skills/ui-styling/SKILL.md +324 -0
- package/assets/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/assets/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/assets/skills/ui-styling/references/shadcn-components.md +424 -0
- package/assets/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/assets/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/assets/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/assets/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/assets/skills/ui-styling/scripts/requirements.txt +17 -0
- package/assets/skills/ui-styling/scripts/shadcn_add.py +308 -0
- package/assets/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
- package/assets/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/assets/skills/ui-styling/scripts/tests/requirements.txt +3 -0
- package/assets/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- package/assets/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
- package/assets/templates/base/quick-reference.md +297 -0
- package/assets/templates/base/skill-content.md +384 -0
- package/assets/templates/platforms/agent.json +21 -0
- package/assets/templates/platforms/augment.json +18 -0
- package/assets/templates/platforms/claude.json +21 -0
- package/assets/templates/platforms/codebuddy.json +21 -0
- package/assets/templates/platforms/codewhale.json +21 -0
- package/assets/templates/platforms/codex.json +21 -0
- package/assets/templates/platforms/continue.json +21 -0
- package/assets/templates/platforms/copilot.json +21 -0
- package/assets/templates/platforms/cursor.json +21 -0
- package/assets/templates/platforms/droid.json +21 -0
- package/assets/templates/platforms/gemini.json +21 -0
- package/assets/templates/platforms/kilocode.json +21 -0
- package/assets/templates/platforms/kiro.json +21 -0
- package/assets/templates/platforms/opencode.json +21 -0
- package/assets/templates/platforms/qoder.json +21 -0
- package/assets/templates/platforms/roocode.json +21 -0
- package/assets/templates/platforms/trae.json +21 -0
- package/assets/templates/platforms/warp.json +18 -0
- package/assets/templates/platforms/windsurf.json +21 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/uninstall.d.ts +7 -0
- package/dist/commands/uninstall.js +131 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +73 -0
- package/dist/commands/versions.d.ts +5 -0
- package/dist/commands/versions.js +36 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +78 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.js +25 -0
- package/dist/utils/detect.d.ts +8 -0
- package/dist/utils/detect.js +115 -0
- package/dist/utils/extract.d.ts +15 -0
- package/dist/utils/extract.js +125 -0
- package/dist/utils/github.d.ts +12 -0
- package/dist/utils/github.js +97 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +9 -0
- package/dist/utils/template.d.ts +50 -0
- package/dist/utils/template.js +249 -0
- package/package.json +65 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* extract-colors.cjs
|
|
4
|
+
*
|
|
5
|
+
* Extract dominant colors from an image and compare against brand palette.
|
|
6
|
+
* Uses pure Node.js without external image processing dependencies.
|
|
7
|
+
*
|
|
8
|
+
* For full color extraction from images, integrate with ai-multimodal skill
|
|
9
|
+
* or use ImageMagick via shell commands.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node extract-colors.cjs <image-path>
|
|
13
|
+
* node extract-colors.cjs <image-path> --brand-file <path>
|
|
14
|
+
* node extract-colors.cjs --palette # Show brand palette from guidelines
|
|
15
|
+
*
|
|
16
|
+
* Integration:
|
|
17
|
+
* For image color analysis, use: ai-multimodal skill or ImageMagick
|
|
18
|
+
* magick <image> -colors 10 -depth 8 -format "%c" histogram:info:
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
// Default brand guidelines path
|
|
25
|
+
const DEFAULT_GUIDELINES_PATH = "docs/brand-guidelines.md";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract hex colors from markdown content
|
|
29
|
+
*/
|
|
30
|
+
function extractHexColors(text) {
|
|
31
|
+
const hexPattern = /#[0-9A-Fa-f]{6}\b/g;
|
|
32
|
+
return [...new Set(text.match(hexPattern) || [])];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse brand guidelines for color palette
|
|
37
|
+
*/
|
|
38
|
+
function parseBrandColors(guidelinesPath) {
|
|
39
|
+
const resolvedPath = path.isAbsolute(guidelinesPath)
|
|
40
|
+
? guidelinesPath
|
|
41
|
+
: path.join(process.cwd(), guidelinesPath);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
48
|
+
|
|
49
|
+
const palette = {
|
|
50
|
+
primary: [],
|
|
51
|
+
secondary: [],
|
|
52
|
+
neutral: [],
|
|
53
|
+
semantic: [],
|
|
54
|
+
all: [],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Extract colors from different sections
|
|
58
|
+
const sections = [
|
|
59
|
+
{ name: "primary", regex: /### Primary[\s\S]*?(?=###|##|$)/i },
|
|
60
|
+
{ name: "secondary", regex: /### Secondary[\s\S]*?(?=###|##|$)/i },
|
|
61
|
+
{ name: "neutral", regex: /### Neutral[\s\S]*?(?=###|##|$)/i },
|
|
62
|
+
{ name: "semantic", regex: /### Semantic[\s\S]*?(?=###|##|$)/i },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
sections.forEach(({ name, regex }) => {
|
|
66
|
+
const match = content.match(regex);
|
|
67
|
+
if (match) {
|
|
68
|
+
const colors = extractHexColors(match[0]);
|
|
69
|
+
palette[name] = colors;
|
|
70
|
+
palette.all.push(...colors);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Dedupe all
|
|
75
|
+
palette.all = [...new Set(palette.all)];
|
|
76
|
+
|
|
77
|
+
return palette;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert hex to RGB
|
|
82
|
+
*/
|
|
83
|
+
function hexToRgb(hex) {
|
|
84
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
85
|
+
return result
|
|
86
|
+
? {
|
|
87
|
+
r: parseInt(result[1], 16),
|
|
88
|
+
g: parseInt(result[2], 16),
|
|
89
|
+
b: parseInt(result[3], 16),
|
|
90
|
+
}
|
|
91
|
+
: null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert RGB to hex
|
|
96
|
+
*/
|
|
97
|
+
function rgbToHex(r, g, b) {
|
|
98
|
+
return (
|
|
99
|
+
"#" +
|
|
100
|
+
[r, g, b]
|
|
101
|
+
.map((x) => {
|
|
102
|
+
const hex = Math.round(x).toString(16);
|
|
103
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
104
|
+
})
|
|
105
|
+
.join("")
|
|
106
|
+
.toUpperCase()
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Calculate color distance (Euclidean in RGB space)
|
|
112
|
+
*/
|
|
113
|
+
function colorDistance(color1, color2) {
|
|
114
|
+
const rgb1 = typeof color1 === "string" ? hexToRgb(color1) : color1;
|
|
115
|
+
const rgb2 = typeof color2 === "string" ? hexToRgb(color2) : color2;
|
|
116
|
+
|
|
117
|
+
if (!rgb1 || !rgb2) return Infinity;
|
|
118
|
+
|
|
119
|
+
return Math.sqrt(
|
|
120
|
+
Math.pow(rgb1.r - rgb2.r, 2) +
|
|
121
|
+
Math.pow(rgb1.g - rgb2.g, 2) +
|
|
122
|
+
Math.pow(rgb1.b - rgb2.b, 2)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Find nearest brand color
|
|
128
|
+
*/
|
|
129
|
+
function findNearestBrandColor(color, brandColors) {
|
|
130
|
+
let nearest = null;
|
|
131
|
+
let minDistance = Infinity;
|
|
132
|
+
|
|
133
|
+
brandColors.forEach((brandColor) => {
|
|
134
|
+
const distance = colorDistance(color, brandColor);
|
|
135
|
+
if (distance < minDistance) {
|
|
136
|
+
minDistance = distance;
|
|
137
|
+
nearest = brandColor;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return { color: nearest, distance: minDistance };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calculate brand compliance percentage
|
|
146
|
+
* Distance threshold: 50 (out of max ~441 for RGB)
|
|
147
|
+
*/
|
|
148
|
+
function calculateCompliance(extractedColors, brandColors, threshold = 50) {
|
|
149
|
+
if (!extractedColors || extractedColors.length === 0) return 100;
|
|
150
|
+
if (!brandColors || brandColors.length === 0) return 0;
|
|
151
|
+
|
|
152
|
+
let matchCount = 0;
|
|
153
|
+
|
|
154
|
+
extractedColors.forEach((color) => {
|
|
155
|
+
const nearest = findNearestBrandColor(color, brandColors);
|
|
156
|
+
if (nearest.distance <= threshold) {
|
|
157
|
+
matchCount++;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return Math.round((matchCount / extractedColors.length) * 100);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate ImageMagick command for color extraction
|
|
166
|
+
*/
|
|
167
|
+
function generateImageMagickCommand(imagePath, numColors = 10) {
|
|
168
|
+
return `magick "${imagePath}" -colors ${numColors} -depth 8 -format "%c" histogram:info:`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Parse ImageMagick histogram output to extract colors
|
|
173
|
+
*/
|
|
174
|
+
function parseImageMagickOutput(output) {
|
|
175
|
+
const colors = [];
|
|
176
|
+
const lines = output.trim().split("\n");
|
|
177
|
+
|
|
178
|
+
lines.forEach((line) => {
|
|
179
|
+
// Match pattern like: 12345: (255,128,64) #FF8040 srgb(255,128,64)
|
|
180
|
+
const hexMatch = line.match(/#([0-9A-Fa-f]{6})/);
|
|
181
|
+
const countMatch = line.match(/^\s*(\d+):/);
|
|
182
|
+
|
|
183
|
+
if (hexMatch) {
|
|
184
|
+
colors.push({
|
|
185
|
+
hex: "#" + hexMatch[1].toUpperCase(),
|
|
186
|
+
count: countMatch ? parseInt(countMatch[1]) : 0,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Sort by count (most common first)
|
|
192
|
+
colors.sort((a, b) => b.count - a.count);
|
|
193
|
+
|
|
194
|
+
return colors;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Display brand palette
|
|
199
|
+
*/
|
|
200
|
+
function displayPalette(palette) {
|
|
201
|
+
console.log("\n" + "=".repeat(50));
|
|
202
|
+
console.log("BRAND COLOR PALETTE");
|
|
203
|
+
console.log("=".repeat(50));
|
|
204
|
+
|
|
205
|
+
if (palette.primary.length > 0) {
|
|
206
|
+
console.log("\nPrimary Colors:");
|
|
207
|
+
palette.primary.forEach((c) => console.log(` ${c}`));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (palette.secondary.length > 0) {
|
|
211
|
+
console.log("\nSecondary Colors:");
|
|
212
|
+
palette.secondary.forEach((c) => console.log(` ${c}`));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (palette.neutral.length > 0) {
|
|
216
|
+
console.log("\nNeutral Colors:");
|
|
217
|
+
palette.neutral.forEach((c) => console.log(` ${c}`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (palette.semantic.length > 0) {
|
|
221
|
+
console.log("\nSemantic Colors:");
|
|
222
|
+
palette.semantic.forEach((c) => console.log(` ${c}`));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log("\n" + "=".repeat(50));
|
|
226
|
+
console.log(`Total: ${palette.all.length} colors in brand palette`);
|
|
227
|
+
console.log("=".repeat(50) + "\n");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Main function
|
|
232
|
+
*/
|
|
233
|
+
function main() {
|
|
234
|
+
const args = process.argv.slice(2);
|
|
235
|
+
const jsonOutput = args.includes("--json");
|
|
236
|
+
const showPalette = args.includes("--palette");
|
|
237
|
+
const brandFileIdx = args.indexOf("--brand-file");
|
|
238
|
+
const brandFile =
|
|
239
|
+
brandFileIdx !== -1 ? args[brandFileIdx + 1] : DEFAULT_GUIDELINES_PATH;
|
|
240
|
+
const brandFileValue = brandFileIdx !== -1 ? args[brandFileIdx + 1] : null;
|
|
241
|
+
const imagePath = args.find(
|
|
242
|
+
(a) => !a.startsWith("--") && a !== brandFileValue
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Load brand palette
|
|
246
|
+
const brandPalette = parseBrandColors(brandFile);
|
|
247
|
+
|
|
248
|
+
if (!brandPalette) {
|
|
249
|
+
console.error(`Brand guidelines not found at: ${brandFile}`);
|
|
250
|
+
console.error(`Create brand guidelines or specify path with --brand-file`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Show palette mode
|
|
255
|
+
if (showPalette || !imagePath) {
|
|
256
|
+
if (jsonOutput) {
|
|
257
|
+
console.log(JSON.stringify(brandPalette, null, 2));
|
|
258
|
+
} else {
|
|
259
|
+
displayPalette(brandPalette);
|
|
260
|
+
|
|
261
|
+
if (!imagePath) {
|
|
262
|
+
console.log("To extract colors from an image:");
|
|
263
|
+
console.log(" node extract-colors.cjs <image-path>");
|
|
264
|
+
console.log("\nOr use ImageMagick directly:");
|
|
265
|
+
console.log(' magick image.png -colors 10 -depth 8 -format "%c" histogram:info:');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Resolve image path
|
|
272
|
+
const resolvedPath = path.isAbsolute(imagePath)
|
|
273
|
+
? imagePath
|
|
274
|
+
: path.join(process.cwd(), imagePath);
|
|
275
|
+
|
|
276
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
277
|
+
console.error(`Image not found: ${resolvedPath}`);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Generate extraction instructions
|
|
282
|
+
const result = {
|
|
283
|
+
image: resolvedPath,
|
|
284
|
+
brandPalette: brandPalette,
|
|
285
|
+
extractionCommand: generateImageMagickCommand(resolvedPath),
|
|
286
|
+
instructions: [
|
|
287
|
+
"1. Run the ImageMagick command to extract colors:",
|
|
288
|
+
` ${generateImageMagickCommand(resolvedPath)}`,
|
|
289
|
+
"",
|
|
290
|
+
"2. Or use the ai-multimodal skill:",
|
|
291
|
+
` python .claude/skills/ai-multimodal/scripts/gemini_batch_process.py \\`,
|
|
292
|
+
` --files "${resolvedPath}" \\`,
|
|
293
|
+
` --task analyze \\`,
|
|
294
|
+
` --prompt "Extract the 10 most dominant colors as hex values"`,
|
|
295
|
+
"",
|
|
296
|
+
"3. Then compare extracted colors against brand palette",
|
|
297
|
+
],
|
|
298
|
+
complianceCheck: {
|
|
299
|
+
threshold: 50,
|
|
300
|
+
description:
|
|
301
|
+
"Colors within distance 50 (RGB space) are considered brand-compliant",
|
|
302
|
+
brandColors: brandPalette.all,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
if (jsonOutput) {
|
|
307
|
+
console.log(JSON.stringify(result, null, 2));
|
|
308
|
+
} else {
|
|
309
|
+
console.log("\n" + "=".repeat(60));
|
|
310
|
+
console.log("COLOR EXTRACTION HELPER");
|
|
311
|
+
console.log("=".repeat(60));
|
|
312
|
+
console.log(`\nImage: ${result.image}`);
|
|
313
|
+
console.log(`\nBrand Colors: ${brandPalette.all.length} colors loaded`);
|
|
314
|
+
console.log("\nTo extract colors from this image:\n");
|
|
315
|
+
result.instructions.forEach((line) => console.log(line));
|
|
316
|
+
console.log("\n" + "=".repeat(60));
|
|
317
|
+
|
|
318
|
+
// Show brand palette for reference
|
|
319
|
+
console.log("\nBrand Palette Reference:");
|
|
320
|
+
console.log(` Primary: ${brandPalette.primary.join(", ") || "none"}`);
|
|
321
|
+
console.log(` Secondary: ${brandPalette.secondary.join(", ") || "none"}`);
|
|
322
|
+
console.log(` Neutral: ${brandPalette.neutral.join(", ") || "none"}`);
|
|
323
|
+
console.log("=".repeat(60) + "\n");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Export functions for use as module
|
|
328
|
+
module.exports = {
|
|
329
|
+
parseBrandColors,
|
|
330
|
+
hexToRgb,
|
|
331
|
+
rgbToHex,
|
|
332
|
+
colorDistance,
|
|
333
|
+
findNearestBrandColor,
|
|
334
|
+
calculateCompliance,
|
|
335
|
+
parseImageMagickOutput,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Run if called directly
|
|
339
|
+
if (require.main === module) {
|
|
340
|
+
main();
|
|
341
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* inject-brand-context.cjs
|
|
4
|
+
*
|
|
5
|
+
* Extracts brand context from markdown brand guidelines
|
|
6
|
+
* and outputs a formatted system prompt addition.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node inject-brand-context.cjs [path-to-guidelines]
|
|
10
|
+
* node inject-brand-context.cjs --json [path-to-guidelines]
|
|
11
|
+
*
|
|
12
|
+
* Default path: docs/brand-guidelines.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
// Default brand guidelines path
|
|
19
|
+
const DEFAULT_GUIDELINES_PATH = "docs/brand-guidelines.md";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract hex colors from text
|
|
23
|
+
*/
|
|
24
|
+
function extractHexColors(text) {
|
|
25
|
+
const hexPattern = /#[0-9A-Fa-f]{6}\b/g;
|
|
26
|
+
return [...new Set(text.match(hexPattern) || [])];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract color data from markdown table
|
|
31
|
+
*/
|
|
32
|
+
function extractColorsFromTable(content) {
|
|
33
|
+
const colors = {
|
|
34
|
+
primary: [],
|
|
35
|
+
secondary: [],
|
|
36
|
+
neutral: [],
|
|
37
|
+
semantic: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Find color tables
|
|
41
|
+
const primaryMatch = content.match(
|
|
42
|
+
/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
|
43
|
+
);
|
|
44
|
+
const secondaryMatch = content.match(
|
|
45
|
+
/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
|
46
|
+
);
|
|
47
|
+
const neutralMatch = content.match(
|
|
48
|
+
/### Neutral[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
|
49
|
+
);
|
|
50
|
+
const semanticMatch = content.match(
|
|
51
|
+
/### Semantic[\s\S]*?\|[\s\S]*?(?=###|$)/i
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (primaryMatch) colors.primary = extractHexColors(primaryMatch[0]);
|
|
55
|
+
if (secondaryMatch) colors.secondary = extractHexColors(secondaryMatch[0]);
|
|
56
|
+
if (neutralMatch) colors.neutral = extractHexColors(neutralMatch[0]);
|
|
57
|
+
if (semanticMatch) colors.semantic = extractHexColors(semanticMatch[0]);
|
|
58
|
+
|
|
59
|
+
return colors;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract typography info
|
|
64
|
+
*/
|
|
65
|
+
function extractTypography(content) {
|
|
66
|
+
const typography = {
|
|
67
|
+
heading: null,
|
|
68
|
+
body: null,
|
|
69
|
+
mono: null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Look for font definitions
|
|
73
|
+
const headingMatch = content.match(/--font-heading:\s*['"]([^'"]+)['"]/);
|
|
74
|
+
const bodyMatch = content.match(/--font-body:\s*['"]([^'"]+)['"]/);
|
|
75
|
+
const monoMatch = content.match(/--font-mono:\s*['"]([^'"]+)['"]/);
|
|
76
|
+
|
|
77
|
+
// Fallback: look in tables
|
|
78
|
+
const fontStackMatch = content.match(/### Font Stack[\s\S]*?(?=###|##|$)/i);
|
|
79
|
+
if (fontStackMatch) {
|
|
80
|
+
const stackText = fontStackMatch[0];
|
|
81
|
+
const headingAlt = stackText.match(/heading[^']*['"]([^'"]+)['"]/i);
|
|
82
|
+
const bodyAlt = stackText.match(/body[^']*['"]([^'"]+)['"]/i);
|
|
83
|
+
|
|
84
|
+
if (headingAlt) typography.heading = headingAlt[1];
|
|
85
|
+
if (bodyAlt) typography.body = bodyAlt[1];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (headingMatch) typography.heading = headingMatch[1];
|
|
89
|
+
if (bodyMatch) typography.body = bodyMatch[1];
|
|
90
|
+
if (monoMatch) typography.mono = monoMatch[1];
|
|
91
|
+
|
|
92
|
+
return typography;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extract voice/tone information
|
|
97
|
+
*/
|
|
98
|
+
function extractVoice(content) {
|
|
99
|
+
const voice = {
|
|
100
|
+
traits: [],
|
|
101
|
+
prohibited: [],
|
|
102
|
+
personality: "",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Extract personality traits from table
|
|
106
|
+
const personalityMatch = content.match(
|
|
107
|
+
/### Brand Personality[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
|
108
|
+
);
|
|
109
|
+
if (personalityMatch) {
|
|
110
|
+
const traits = personalityMatch[0].match(
|
|
111
|
+
/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/g
|
|
112
|
+
);
|
|
113
|
+
if (traits) {
|
|
114
|
+
voice.traits = traits.map((t) => {
|
|
115
|
+
const match = t.match(/\*\*([^*]+)\*\*/);
|
|
116
|
+
return match ? match[1].trim() : "";
|
|
117
|
+
}).filter(Boolean);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Extract prohibited terms
|
|
122
|
+
const prohibitedMatch = content.match(
|
|
123
|
+
/### Prohibited[\s\S]*?(?=###|##|$)/i
|
|
124
|
+
);
|
|
125
|
+
if (prohibitedMatch) {
|
|
126
|
+
const terms = prohibitedMatch[0].match(/\|\s*([^|]+)\s*\|/g);
|
|
127
|
+
if (terms) {
|
|
128
|
+
voice.prohibited = terms
|
|
129
|
+
.map((t) => t.replace(/\|/g, "").trim())
|
|
130
|
+
.filter((t) => t && !t.includes("Avoid") && !t.includes("---"));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Fallback: look for Forbidden Phrases
|
|
135
|
+
const forbiddenMatch = content.match(
|
|
136
|
+
/### Forbidden Phrases[\s\S]*?(?=###|##|$)/i
|
|
137
|
+
);
|
|
138
|
+
if (forbiddenMatch && voice.prohibited.length === 0) {
|
|
139
|
+
const items = forbiddenMatch[0].match(/-\s*["']?([^"'\n(]+)/g);
|
|
140
|
+
if (items) {
|
|
141
|
+
voice.prohibited = items
|
|
142
|
+
.map((item) => item.replace(/^-\s*["']?/, "").trim())
|
|
143
|
+
.filter(Boolean);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
voice.personality = voice.traits.join(", ");
|
|
148
|
+
|
|
149
|
+
return voice;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extract core attributes
|
|
154
|
+
*/
|
|
155
|
+
function extractCoreAttributes(content) {
|
|
156
|
+
const attributes = [];
|
|
157
|
+
|
|
158
|
+
const attributesMatch = content.match(
|
|
159
|
+
/### Core Attributes[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
|
160
|
+
);
|
|
161
|
+
if (attributesMatch) {
|
|
162
|
+
const rows = attributesMatch[0].match(
|
|
163
|
+
/\|\s*\*\*([^*]+)\*\*\s*\|\s*([^|]+)\|/g
|
|
164
|
+
);
|
|
165
|
+
if (rows) {
|
|
166
|
+
rows.forEach((row) => {
|
|
167
|
+
const match = row.match(/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/);
|
|
168
|
+
if (match) {
|
|
169
|
+
attributes.push({
|
|
170
|
+
name: match[1].trim(),
|
|
171
|
+
description: match[2].trim(),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return attributes;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract AI image generation context
|
|
183
|
+
*/
|
|
184
|
+
function extractImageStyle(content) {
|
|
185
|
+
const imageStyle = {
|
|
186
|
+
basePrompt: "",
|
|
187
|
+
keywords: [],
|
|
188
|
+
mood: [],
|
|
189
|
+
donts: [],
|
|
190
|
+
examplePrompts: [],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Extract base prompt template (content between ``` blocks after "Base Prompt Template")
|
|
194
|
+
const basePromptMatch = content.match(
|
|
195
|
+
/### Base Prompt Template[\s\S]*?```\n?([\s\S]*?)```/i
|
|
196
|
+
);
|
|
197
|
+
if (basePromptMatch) {
|
|
198
|
+
imageStyle.basePrompt = basePromptMatch[1].trim().replace(/\n/g, " ");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Extract style keywords from table
|
|
202
|
+
const keywordsMatch = content.match(
|
|
203
|
+
/### Style Keywords[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
|
204
|
+
);
|
|
205
|
+
if (keywordsMatch) {
|
|
206
|
+
const keywordRows = keywordsMatch[0].match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/g);
|
|
207
|
+
if (keywordRows) {
|
|
208
|
+
keywordRows.forEach((row) => {
|
|
209
|
+
const match = row.match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/);
|
|
210
|
+
if (match) {
|
|
211
|
+
const keywords = match[1].split(",").map((k) => k.trim()).filter(Boolean);
|
|
212
|
+
imageStyle.keywords.push(...keywords);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Extract visual mood descriptors (bullet points)
|
|
219
|
+
const moodMatch = content.match(
|
|
220
|
+
/### Visual Mood Descriptors[\s\S]*?(?=###|##|$)/i
|
|
221
|
+
);
|
|
222
|
+
if (moodMatch) {
|
|
223
|
+
const moodItems = moodMatch[0].match(/-\s*([^\n]+)/g);
|
|
224
|
+
if (moodItems) {
|
|
225
|
+
imageStyle.mood = moodItems.map((item) => item.replace(/^-\s*/, "").trim());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Extract visual don'ts from table
|
|
230
|
+
const dontsMatch = content.match(
|
|
231
|
+
/### Visual Don'ts[\s\S]*?\|[\s\S]*?(?=###|##|$)/i
|
|
232
|
+
);
|
|
233
|
+
if (dontsMatch) {
|
|
234
|
+
const dontRows = dontsMatch[0].match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g);
|
|
235
|
+
if (dontRows) {
|
|
236
|
+
dontRows.forEach((row) => {
|
|
237
|
+
const match = row.match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/);
|
|
238
|
+
if (match && !match[1].includes("Avoid") && !match[1].includes("---")) {
|
|
239
|
+
imageStyle.donts.push(match[1].trim());
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Extract example prompts (content between ``` blocks after specific headers)
|
|
246
|
+
const exampleMatch = content.match(/### Example Prompts[\s\S]*?(?=##|$)/i);
|
|
247
|
+
if (exampleMatch) {
|
|
248
|
+
const prompts = exampleMatch[0].match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/g);
|
|
249
|
+
if (prompts) {
|
|
250
|
+
prompts.forEach((p) => {
|
|
251
|
+
const match = p.match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/);
|
|
252
|
+
if (match) {
|
|
253
|
+
imageStyle.examplePrompts.push({
|
|
254
|
+
type: match[1].trim(),
|
|
255
|
+
prompt: match[2].trim().replace(/\n/g, " "),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return imageStyle;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Generate system prompt addition
|
|
267
|
+
*/
|
|
268
|
+
function generatePromptAddition(brandContext) {
|
|
269
|
+
const { colors, typography, voice, attributes, imageStyle } = brandContext;
|
|
270
|
+
|
|
271
|
+
let prompt = `
|
|
272
|
+
BRAND CONTEXT:
|
|
273
|
+
==============
|
|
274
|
+
|
|
275
|
+
VISUAL IDENTITY:
|
|
276
|
+
- Primary Colors: ${colors.primary.join(", ") || "Not specified"}
|
|
277
|
+
- Secondary Colors: ${colors.secondary.join(", ") || "Not specified"}
|
|
278
|
+
- Typography: ${typography.heading || typography.body || "System fonts"}
|
|
279
|
+
|
|
280
|
+
BRAND VOICE:
|
|
281
|
+
- Personality: ${voice.personality || "Professional"}
|
|
282
|
+
- Core Attributes: ${attributes.map((a) => a.name).join(", ") || "Not specified"}
|
|
283
|
+
|
|
284
|
+
CONTENT RULES:
|
|
285
|
+
- Prohibited Terms: ${voice.prohibited.join(", ") || "None specified"}
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
// Add image style context if available
|
|
289
|
+
if (imageStyle && imageStyle.basePrompt) {
|
|
290
|
+
prompt += `
|
|
291
|
+
IMAGE GENERATION:
|
|
292
|
+
- Base Prompt: ${imageStyle.basePrompt}
|
|
293
|
+
- Style Keywords: ${imageStyle.keywords.slice(0, 10).join(", ") || "Not specified"}
|
|
294
|
+
- Visual Mood: ${imageStyle.mood.slice(0, 5).join("; ") || "Not specified"}
|
|
295
|
+
- Avoid: ${imageStyle.donts.join(", ") || "None specified"}
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
prompt += `
|
|
300
|
+
Apply these brand guidelines to all generated content.
|
|
301
|
+
Maintain consistent voice, colors, and messaging.
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
return prompt.trim();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Main function
|
|
309
|
+
*/
|
|
310
|
+
function main() {
|
|
311
|
+
const args = process.argv.slice(2);
|
|
312
|
+
const jsonOutput = args.includes("--json");
|
|
313
|
+
const guidelinesPath = args.find((a) => !a.startsWith("--")) || DEFAULT_GUIDELINES_PATH;
|
|
314
|
+
|
|
315
|
+
// Resolve path
|
|
316
|
+
const resolvedPath = path.isAbsolute(guidelinesPath)
|
|
317
|
+
? guidelinesPath
|
|
318
|
+
: path.join(process.cwd(), guidelinesPath);
|
|
319
|
+
|
|
320
|
+
// Check if file exists
|
|
321
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
322
|
+
console.error(`Error: Brand guidelines not found at ${resolvedPath}`);
|
|
323
|
+
console.error(`Create brand guidelines at ${DEFAULT_GUIDELINES_PATH} or specify a path.`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Read file
|
|
328
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
329
|
+
|
|
330
|
+
// Extract brand context
|
|
331
|
+
const brandContext = {
|
|
332
|
+
colors: extractColorsFromTable(content),
|
|
333
|
+
typography: extractTypography(content),
|
|
334
|
+
voice: extractVoice(content),
|
|
335
|
+
attributes: extractCoreAttributes(content),
|
|
336
|
+
imageStyle: extractImageStyle(content),
|
|
337
|
+
source: resolvedPath,
|
|
338
|
+
extractedAt: new Date().toISOString(),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Output
|
|
342
|
+
if (jsonOutput) {
|
|
343
|
+
console.log(JSON.stringify(brandContext, null, 2));
|
|
344
|
+
} else {
|
|
345
|
+
console.log(generatePromptAddition(brandContext));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
main();
|