Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
AI-powered brand identity skill for generating cohesive brand guidelines, color palettes, and visual identity.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/inject-brand-context.cjs
1#!/usr/bin/env node2/**3* inject-brand-context.cjs4*5* Extracts brand context from markdown brand guidelines6* 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.md13*/1415const fs = require("fs");16const path = require("path");1718// Default brand guidelines path19const DEFAULT_GUIDELINES_PATH = "docs/brand-guidelines.md";2021/**22* Extract hex colors from text23*/24function extractHexColors(text) {25const hexPattern = /#[0-9A-Fa-f]{6}\b/g;26return [...new Set(text.match(hexPattern) || [])];27}2829/**30* Extract color data from markdown table31*/32function extractColorsFromTable(content) {33const colors = {34primary: [],35secondary: [],36neutral: [],37semantic: [],38};3940// Find color tables41const primaryMatch = content.match(42/### Primary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i43);44const secondaryMatch = content.match(45/### Secondary Colors[\s\S]*?\|[\s\S]*?(?=###|$)/i46);47const neutralMatch = content.match(48/### Neutral[\s\S]*?\|[\s\S]*?(?=###|$)/i49);50const semanticMatch = content.match(51/### Semantic[\s\S]*?\|[\s\S]*?(?=###|$)/i52);5354if (primaryMatch) colors.primary = extractHexColors(primaryMatch[0]);55if (secondaryMatch) colors.secondary = extractHexColors(secondaryMatch[0]);56if (neutralMatch) colors.neutral = extractHexColors(neutralMatch[0]);57if (semanticMatch) colors.semantic = extractHexColors(semanticMatch[0]);5859return colors;60}6162/**63* Extract typography info64*/65function extractTypography(content) {66const typography = {67heading: null,68body: null,69mono: null,70};7172// Look for font definitions73const headingMatch = content.match(/--font-heading:\s*['"]([^'"]+)['"]/);74const bodyMatch = content.match(/--font-body:\s*['"]([^'"]+)['"]/);75const monoMatch = content.match(/--font-mono:\s*['"]([^'"]+)['"]/);7677// Fallback: look in tables78const fontStackMatch = content.match(/### Font Stack[\s\S]*?(?=###|##|$)/i);79if (fontStackMatch) {80const stackText = fontStackMatch[0];81const headingAlt = stackText.match(/heading[^']*['"]([^'"]+)['"]/i);82const bodyAlt = stackText.match(/body[^']*['"]([^'"]+)['"]/i);8384if (headingAlt) typography.heading = headingAlt[1];85if (bodyAlt) typography.body = bodyAlt[1];86}8788if (headingMatch) typography.heading = headingMatch[1];89if (bodyMatch) typography.body = bodyMatch[1];90if (monoMatch) typography.mono = monoMatch[1];9192return typography;93}9495/**96* Extract voice/tone information97*/98function extractVoice(content) {99const voice = {100traits: [],101prohibited: [],102personality: "",103};104105// Extract personality traits from table106const personalityMatch = content.match(107/### Brand Personality[\s\S]*?\|[\s\S]*?(?=###|##|$)/i108);109if (personalityMatch) {110const traits = personalityMatch[0].match(111/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/g112);113if (traits) {114voice.traits = traits.map((t) => {115const match = t.match(/\*\*([^*]+)\*\*/);116return match ? match[1].trim() : "";117}).filter(Boolean);118}119}120121// Extract prohibited terms122const prohibitedMatch = content.match(123/### Prohibited[\s\S]*?(?=###|##|$)/i124);125if (prohibitedMatch) {126const terms = prohibitedMatch[0].match(/\|\s*([^|]+)\s*\|/g);127if (terms) {128voice.prohibited = terms129.map((t) => t.replace(/\|/g, "").trim())130.filter((t) => t && !t.includes("Avoid") && !t.includes("---"));131}132}133134// Fallback: look for Forbidden Phrases135const forbiddenMatch = content.match(136/### Forbidden Phrases[\s\S]*?(?=###|##|$)/i137);138if (forbiddenMatch && voice.prohibited.length === 0) {139const items = forbiddenMatch[0].match(/-\s*["']?([^"'\n(]+)/g);140if (items) {141voice.prohibited = items142.map((item) => item.replace(/^-\s*["']?/, "").trim())143.filter(Boolean);144}145}146147voice.personality = voice.traits.join(", ");148149return voice;150}151152/**153* Extract core attributes154*/155function extractCoreAttributes(content) {156const attributes = [];157158const attributesMatch = content.match(159/### Core Attributes[\s\S]*?\|[\s\S]*?(?=###|##|$)/i160);161if (attributesMatch) {162const rows = attributesMatch[0].match(163/\|\s*\*\*([^*]+)\*\*\s*\|\s*([^|]+)\|/g164);165if (rows) {166rows.forEach((row) => {167const match = row.match(/\*\*([^*]+)\*\*\s*\|\s*([^|]+)/);168if (match) {169attributes.push({170name: match[1].trim(),171description: match[2].trim(),172});173}174});175}176}177178return attributes;179}180181/**182* Extract AI image generation context183*/184function extractImageStyle(content) {185const imageStyle = {186basePrompt: "",187keywords: [],188mood: [],189donts: [],190examplePrompts: [],191};192193// Extract base prompt template (content between ``` blocks after "Base Prompt Template")194const basePromptMatch = content.match(195/### Base Prompt Template[\s\S]*?```\n?([\s\S]*?)```/i196);197if (basePromptMatch) {198imageStyle.basePrompt = basePromptMatch[1].trim().replace(/\n/g, " ");199}200201// Extract style keywords from table202const keywordsMatch = content.match(203/### Style Keywords[\s\S]*?\|[\s\S]*?(?=###|##|$)/i204);205if (keywordsMatch) {206const keywordRows = keywordsMatch[0].match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/g);207if (keywordRows) {208keywordRows.forEach((row) => {209const match = row.match(/\|\s*\*\*[^*]+\*\*\s*\|\s*([^|]+)\|/);210if (match) {211const keywords = match[1].split(",").map((k) => k.trim()).filter(Boolean);212imageStyle.keywords.push(...keywords);213}214});215}216}217218// Extract visual mood descriptors (bullet points)219const moodMatch = content.match(220/### Visual Mood Descriptors[\s\S]*?(?=###|##|$)/i221);222if (moodMatch) {223const moodItems = moodMatch[0].match(/-\s*([^\n]+)/g);224if (moodItems) {225imageStyle.mood = moodItems.map((item) => item.replace(/^-\s*/, "").trim());226}227}228229// Extract visual don'ts from table230const dontsMatch = content.match(231/### Visual Don'ts[\s\S]*?\|[\s\S]*?(?=###|##|$)/i232);233if (dontsMatch) {234const dontRows = dontsMatch[0].match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g);235if (dontRows) {236dontRows.forEach((row) => {237const match = row.match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/);238if (match && !match[1].includes("Avoid") && !match[1].includes("---")) {239imageStyle.donts.push(match[1].trim());240}241});242}243}244245// Extract example prompts (content between ``` blocks after specific headers)246const exampleMatch = content.match(/### Example Prompts[\s\S]*?(?=##|$)/i);247if (exampleMatch) {248const prompts = exampleMatch[0].match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/g);249if (prompts) {250prompts.forEach((p) => {251const match = p.match(/\*\*([^*]+)\*\*:\s*```\n?([\s\S]*?)```/);252if (match) {253imageStyle.examplePrompts.push({254type: match[1].trim(),255prompt: match[2].trim().replace(/\n/g, " "),256});257}258});259}260}261262return imageStyle;263}264265/**266* Generate system prompt addition267*/268function generatePromptAddition(brandContext) {269const { colors, typography, voice, attributes, imageStyle } = brandContext;270271let prompt = `272BRAND CONTEXT:273==============274275VISUAL 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"}279280BRAND VOICE:281- Personality: ${voice.personality || "Professional"}282- Core Attributes: ${attributes.map((a) => a.name).join(", ") || "Not specified"}283284CONTENT RULES:285- Prohibited Terms: ${voice.prohibited.join(", ") || "None specified"}286`;287288// Add image style context if available289if (imageStyle && imageStyle.basePrompt) {290prompt += `291IMAGE 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}298299prompt += `300Apply these brand guidelines to all generated content.301Maintain consistent voice, colors, and messaging.302`;303304return prompt.trim();305}306307/**308* Main function309*/310function main() {311const args = process.argv.slice(2);312const jsonOutput = args.includes("--json");313const guidelinesPath = args.find((a) => !a.startsWith("--")) || DEFAULT_GUIDELINES_PATH;314315// Resolve path316const resolvedPath = path.isAbsolute(guidelinesPath)317? guidelinesPath318: path.join(process.cwd(), guidelinesPath);319320// Check if file exists321if (!fs.existsSync(resolvedPath)) {322console.error(`Error: Brand guidelines not found at ${resolvedPath}`);323console.error(`Create brand guidelines at ${DEFAULT_GUIDELINES_PATH} or specify a path.`);324process.exit(1);325}326327// Read file328const content = fs.readFileSync(resolvedPath, "utf-8");329330// Extract brand context331const brandContext = {332colors: extractColorsFromTable(content),333typography: extractTypography(content),334voice: extractVoice(content),335attributes: extractCoreAttributes(content),336imageStyle: extractImageStyle(content),337source: resolvedPath,338extractedAt: new Date().toISOString(),339};340341// Output342if (jsonOutput) {343console.log(JSON.stringify(brandContext, null, 2));344} else {345console.log(generatePromptAddition(brandContext));346}347}348349main();350