| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.generateRandomDesignSystem = generateRandomDesignSystem; | ||
| const designSystemSchema_1 = require("../domain/designSystemSchema"); | ||
| const PRODUCT_PREFIXES = [ | ||
| "Atlas", | ||
| "Nova", | ||
| "Lumen", | ||
| "Vertex", | ||
| "Nimbus", | ||
| "Harbor", | ||
| "Prism", | ||
| "Cobalt", | ||
| "Echo", | ||
| "Forge", | ||
| "Pulse", | ||
| "Aster" | ||
| ]; | ||
| const PRODUCT_SUFFIXES = [ | ||
| "UI", | ||
| "Cloud", | ||
| "Flow", | ||
| "Studio", | ||
| "Works", | ||
| "Console", | ||
| "Labs", | ||
| "Desk", | ||
| "Board", | ||
| "Portal", | ||
| "Suite", | ||
| "System" | ||
| ]; | ||
| const VISUAL_STYLE_OPTIONS = [ | ||
| "modern", | ||
| "minimal", | ||
| "clean", | ||
| "high-contrast", | ||
| "bold", | ||
| "playful", | ||
| "editorial", | ||
| "data-dense", | ||
| "enterprise", | ||
| "premium" | ||
| ]; | ||
| const TYPOGRAPHY_SCALE_OPTIONS = [ | ||
| "12/14/16/20/24/32", | ||
| "12/14/16/18/24/30/36", | ||
| "13/15/17/21/27/35", | ||
| "14/16/18/24/32/40", | ||
| "mobile-first compact scale", | ||
| "desktop-first expressive scale" | ||
| ]; | ||
| const FONT_WEIGHT_OPTIONS = [ | ||
| "100", | ||
| "200", | ||
| "300", | ||
| "400", | ||
| "500", | ||
| "600", | ||
| "700", | ||
| "800", | ||
| "900" | ||
| ]; | ||
| const GOOGLE_FONT_SANS_OPTIONS = [ | ||
| "Inter", | ||
| "Roboto", | ||
| "Open Sans", | ||
| "Lato", | ||
| "Montserrat", | ||
| "Poppins", | ||
| "Nunito", | ||
| "Work Sans", | ||
| "Source Sans 3", | ||
| "Plus Jakarta Sans", | ||
| "Archivo", | ||
| "Barlow", | ||
| "Kanit", | ||
| "M PLUS 1p", | ||
| "Raleway", | ||
| "PT Sans", | ||
| "Ubuntu", | ||
| "Cabin", | ||
| "Hind", | ||
| "Public Sans", | ||
| "Mulish", | ||
| "Quicksand", | ||
| "Lexend", | ||
| "Manrope", | ||
| "Noto Sans", | ||
| "DM Sans", | ||
| "Rubik", | ||
| "Urbanist" | ||
| ]; | ||
| const GOOGLE_FONT_DISPLAY_OPTIONS = [ | ||
| "Inter", | ||
| "Montserrat", | ||
| "Poppins", | ||
| "Space Grotesk", | ||
| "Plus Jakarta Sans", | ||
| "Outfit", | ||
| "Playfair Display", | ||
| "Merriweather", | ||
| "Bebas Neue", | ||
| "Raleway", | ||
| "Oswald", | ||
| "Archivo", | ||
| "Anton", | ||
| "Bricolage Grotesque", | ||
| "Sora", | ||
| "Figtree", | ||
| "Josefin Sans", | ||
| "Lora", | ||
| "Archivo Black", | ||
| "Abril Fatface", | ||
| "Cormorant Garamond", | ||
| "DM Serif Display" | ||
| ]; | ||
| const GOOGLE_FONT_MONO_OPTIONS = [ | ||
| "JetBrains Mono", | ||
| "Fira Code", | ||
| "Source Code Pro", | ||
| "IBM Plex Mono", | ||
| "Inconsolata", | ||
| "Space Mono", | ||
| "Roboto Mono", | ||
| "Ubuntu Mono", | ||
| "Fira Mono", | ||
| "Cousine", | ||
| "PT Mono", | ||
| "Anonymous Pro", | ||
| "Overpass Mono" | ||
| ]; | ||
| const COLOR_PALETTE_OPTIONS = [ | ||
| "primary", | ||
| "secondary", | ||
| "neutral", | ||
| "success", | ||
| "warning", | ||
| "danger", | ||
| "info", | ||
| "surface/subtle layers", | ||
| "dark mode parity" | ||
| ]; | ||
| const SPACING_SCALE_OPTIONS = [ | ||
| "4/8/12/16/24/32", | ||
| "2/4/8/12/16/24/32/48", | ||
| "8pt baseline grid", | ||
| "compact density mode", | ||
| "comfortable density mode" | ||
| ]; | ||
| const ACCESSIBILITY_OPTIONS = [ | ||
| "WCAG 2.2 AA", | ||
| "keyboard-first interactions", | ||
| "visible focus states", | ||
| "semantic HTML before ARIA", | ||
| "screen-reader tested labels", | ||
| "reduced-motion support", | ||
| "44px+ touch targets", | ||
| "high-contrast support" | ||
| ]; | ||
| const WRITING_TONE_OPTIONS = [ | ||
| "concise", | ||
| "confident", | ||
| "helpful", | ||
| "clear", | ||
| "friendly", | ||
| "professional", | ||
| "action-oriented", | ||
| "low-jargon" | ||
| ]; | ||
| const DO_RULE_OPTIONS = [ | ||
| "prefer semantic tokens over raw values", | ||
| "preserve visual hierarchy", | ||
| "keep interaction states explicit", | ||
| "design for empty/loading/error states", | ||
| "ensure responsive behavior by default", | ||
| "document accessibility rationale" | ||
| ]; | ||
| const DONT_RULE_OPTIONS = [ | ||
| "avoid low contrast text", | ||
| "avoid inconsistent spacing rhythm", | ||
| "avoid decorative motion without purpose", | ||
| "avoid ambiguous labels", | ||
| "avoid mixing multiple visual metaphors", | ||
| "avoid inaccessible hit areas" | ||
| ]; | ||
| const BRAND_AUDIENCES = [ | ||
| "product", | ||
| "engineering", | ||
| "design systems", | ||
| "growth", | ||
| "operations", | ||
| "support" | ||
| ]; | ||
| const BRAND_DOMAINS = [ | ||
| "dashboard", | ||
| "admin", | ||
| "commerce", | ||
| "analytics", | ||
| "collaboration", | ||
| "publishing", | ||
| "developer tooling", | ||
| "ops automation" | ||
| ]; | ||
| function randomInt(min, max) { | ||
| return Math.floor(Math.random() * (max - min + 1)) + min; | ||
| } | ||
| function sampleOne(values) { | ||
| return values[randomInt(0, values.length - 1)]; | ||
| } | ||
| function sampleMany(values, minCount, maxCount) { | ||
| const count = randomInt(minCount, Math.min(maxCount, values.length)); | ||
| const shuffled = [...values]; | ||
| for (let i = shuffled.length - 1; i > 0; i -= 1) { | ||
| const j = randomInt(0, i); | ||
| [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; | ||
| } | ||
| return shuffled.slice(0, count); | ||
| } | ||
| function hslToHex(hue, saturation, lightness) { | ||
| const s = saturation / 100; | ||
| const l = lightness / 100; | ||
| const c = (1 - Math.abs(2 * l - 1)) * s; | ||
| const x = c * (1 - Math.abs(((hue / 60) % 2) - 1)); | ||
| const m = l - c / 2; | ||
| let r = 0; | ||
| let g = 0; | ||
| let b = 0; | ||
| if (hue < 60) { | ||
| r = c; | ||
| g = x; | ||
| } | ||
| else if (hue < 120) { | ||
| r = x; | ||
| g = c; | ||
| } | ||
| else if (hue < 180) { | ||
| g = c; | ||
| b = x; | ||
| } | ||
| else if (hue < 240) { | ||
| g = x; | ||
| b = c; | ||
| } | ||
| else if (hue < 300) { | ||
| r = x; | ||
| b = c; | ||
| } | ||
| else { | ||
| r = c; | ||
| b = x; | ||
| } | ||
| const toHex = (value) => Math.round((value + m) * 255) | ||
| .toString(16) | ||
| .padStart(2, "0") | ||
| .toUpperCase(); | ||
| return `#${toHex(r)}${toHex(g)}${toHex(b)}`; | ||
| } | ||
| function generateColorTokens() { | ||
| const baseHue = randomInt(0, 359); | ||
| const primary = hslToHex(baseHue, randomInt(58, 84), randomInt(32, 46)); | ||
| const secondary = hslToHex((baseHue + randomInt(32, 92)) % 360, randomInt(60, 88), randomInt(38, 55)); | ||
| const success = hslToHex(randomInt(122, 145), randomInt(55, 75), randomInt(34, 46)); | ||
| const warning = hslToHex(randomInt(30, 44), randomInt(72, 90), randomInt(42, 56)); | ||
| const danger = hslToHex(randomInt(2, 15), randomInt(65, 88), randomInt(42, 55)); | ||
| const surface = hslToHex(baseHue, randomInt(10, 24), randomInt(95, 99)); | ||
| const text = hslToHex(baseHue, randomInt(18, 34), randomInt(11, 18)); | ||
| return { primary, secondary, success, warning, danger, surface, text }; | ||
| } | ||
| function generateProductName() { | ||
| return `${sampleOne(PRODUCT_PREFIXES)} ${sampleOne(PRODUCT_SUFFIXES)}`; | ||
| } | ||
| function generateBrandSummary(productName, visualStyle) { | ||
| const audience = sampleOne(BRAND_AUDIENCES); | ||
| const domain = sampleOne(BRAND_DOMAINS); | ||
| const direction = visualStyle.split(",")[0]?.trim() || visualStyle; | ||
| return `${productName} delivers ${direction} interface guidance for ${domain} workflows, helping ${audience} teams ship consistently and accessibly.`; | ||
| } | ||
| function generateTypographyScale() { | ||
| const scale = sampleOne(TYPOGRAPHY_SCALE_OPTIONS); | ||
| const primary = sampleOne(GOOGLE_FONT_SANS_OPTIONS); | ||
| const display = sampleOne(GOOGLE_FONT_DISPLAY_OPTIONS); | ||
| const mono = sampleOne(GOOGLE_FONT_MONO_OPTIONS); | ||
| const weights = sampleMany(FONT_WEIGHT_OPTIONS, 4, 7).sort((a, b) => Number(a) - Number(b)); | ||
| return `${scale} | Fonts: primary=${primary}, display=${display}, mono=${mono} | weights=${weights.join(", ")}`; | ||
| } | ||
| function generateColorPalette() { | ||
| const required = ["primary", "neutral"]; | ||
| const optional = COLOR_PALETTE_OPTIONS.filter((value) => !required.includes(value)); | ||
| const selected = [...required, ...sampleMany(optional, 2, 5)]; | ||
| const tokens = generateColorTokens(); | ||
| return (`${selected.join(", ")} | Tokens: primary=${tokens.primary}, secondary=${tokens.secondary}, ` + | ||
| `success=${tokens.success}, warning=${tokens.warning}, danger=${tokens.danger}, ` + | ||
| `surface=${tokens.surface}, text=${tokens.text}`); | ||
| } | ||
| function generateRandomDesignSystem() { | ||
| const visualStyle = sampleMany(VISUAL_STYLE_OPTIONS, 3, 5).join(", "); | ||
| const design = { | ||
| productName: generateProductName(), | ||
| brandSummary: "", | ||
| visualStyle, | ||
| typographyScale: generateTypographyScale(), | ||
| colorPalette: generateColorPalette(), | ||
| spacingScale: sampleOne(SPACING_SCALE_OPTIONS), | ||
| accessibilityRequirements: sampleMany(ACCESSIBILITY_OPTIONS, 3, 5).join(", "), | ||
| writingTone: sampleMany(WRITING_TONE_OPTIONS, 2, 4).join(", "), | ||
| doRules: sampleMany(DO_RULE_OPTIONS, 3, 5), | ||
| dontRules: sampleMany(DONT_RULE_OPTIONS, 3, 5) | ||
| }; | ||
| design.brandSummary = generateBrandSummary(design.productName, visualStyle); | ||
| return designSystemSchema_1.DesignSystemSchema.parse(design); | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.runDesignGeneration = runDesignGeneration; | ||
| const updateSkillFile_1 = require("../io/updateSkillFile"); | ||
| const shared_1 = require("../renderers/shared"); | ||
| async function runDesignGeneration(options) { | ||
| const content = (0, shared_1.createDesignMarkdownFile)(options.designSystem); | ||
| const result = await (0, updateSkillFile_1.writeMarkdownFile)(options.projectRoot, "DESIGN.md", content, options.dryRun ?? false); | ||
| return [ | ||
| { | ||
| filePath: result.absPath, | ||
| changed: result.changed | ||
| } | ||
| ]; | ||
| } |
+97
-15
@@ -13,2 +13,4 @@ #!/usr/bin/env node | ||
| const existingDesignSystem_1 = require("./generation/existingDesignSystem"); | ||
| const randomDesignSystem_1 = require("./generation/randomDesignSystem"); | ||
| const runDesignGeneration_1 = require("./generation/runDesignGeneration"); | ||
| const runGeneration_1 = require("./generation/runGeneration"); | ||
@@ -37,2 +39,15 @@ const runPull_1 = require("./generation/runPull"); | ||
| } | ||
| function parsePullFormatOption(raw) { | ||
| const parsed = designSystemSchema_1.PullFormatSchema.safeParse(raw ?? "skill"); | ||
| if (!parsed.success) { | ||
| throw new Error("Unsupported format. Supported: skill, design."); | ||
| } | ||
| return parsed.data; | ||
| } | ||
| async function resolvePullFormatOption(raw) { | ||
| if (raw) { | ||
| return parsePullFormatOption(raw); | ||
| } | ||
| return (0, registry_1.promptPullFormatSelection)(); | ||
| } | ||
| function printResults(mode, results) { | ||
@@ -45,6 +60,51 @@ console.log(""); | ||
| } | ||
| async function randomizeLike(options) { | ||
| const format = await resolvePullFormatOption(options.format); | ||
| const designSystem = (0, randomDesignSystem_1.generateRandomDesignSystem)(); | ||
| if (format === "design") { | ||
| const results = await (0, runDesignGeneration_1.runDesignGeneration)({ | ||
| projectRoot: process.cwd(), | ||
| designSystem, | ||
| dryRun: Boolean(options.dryRun) | ||
| }); | ||
| printResults(options.dryRun ? "preview" : "randomized", results); | ||
| return; | ||
| } | ||
| const selectedProviders = parseProviderOption(options.providers) ?? (await (0, designSystem_1.promptProviders)()); | ||
| const providers = [...new Set([...types_1.ALWAYS_INCLUDED_PROVIDERS, ...selectedProviders])]; | ||
| const results = await (0, runGeneration_1.runGeneration)({ | ||
| projectRoot: process.cwd(), | ||
| providers, | ||
| designSystem, | ||
| metadata: (0, skillMetadata_1.buildDefaultSkillMetadata)(designSystem.productName), | ||
| dryRun: Boolean(options.dryRun) | ||
| }); | ||
| printResults(options.dryRun ? "preview" : "randomized", results); | ||
| } | ||
| async function generateLike(action, mode, options) { | ||
| const format = await resolvePullFormatOption(options.format); | ||
| let designSystem; | ||
| if (format === "design") { | ||
| if (action === "update") { | ||
| const existing = await (0, existingDesignSystem_1.loadExistingDesignMarkdown)(process.cwd()); | ||
| if (!existing) { | ||
| throw new Error("No existing DESIGN.md found in the project root. Run `typeui.sh generate` first."); | ||
| } | ||
| const fields = await (0, designSystem_1.promptDesignSystemFields)(); | ||
| const updates = await (0, designSystem_1.promptDesignSystemUpdates)(existing, fields); | ||
| designSystem = { ...existing, ...updates }; | ||
| } | ||
| else { | ||
| designSystem = await (0, designSystem_1.promptDesignSystem)("typeui.sh"); | ||
| } | ||
| const results = await (0, runDesignGeneration_1.runDesignGeneration)({ | ||
| projectRoot: process.cwd(), | ||
| designSystem, | ||
| dryRun: Boolean(options.dryRun) | ||
| }); | ||
| printResults(mode, results); | ||
| return; | ||
| } | ||
| const selectedProviders = parseProviderOption(options.providers) ?? (await (0, designSystem_1.promptProviders)()); | ||
| const providers = [...new Set([...types_1.ALWAYS_INCLUDED_PROVIDERS, ...selectedProviders])]; | ||
| let designSystem; | ||
| let metadata = (0, skillMetadata_1.buildDefaultSkillMetadata)("typeui.sh"); | ||
@@ -79,5 +139,12 @@ if (action === "update") { | ||
| } | ||
| const selectedProviders = parseProviderOption(options.providers) ?? (await (0, designSystem_1.promptProviders)()); | ||
| const providers = [...new Set([...types_1.ALWAYS_INCLUDED_PROVIDERS, ...selectedProviders])]; | ||
| const pullResult = await (0, registryClient_1.pullSkillMarkdown)(parsedSlug.data); | ||
| const format = await resolvePullFormatOption(options.format); | ||
| const providers = format === "skill" | ||
| ? [ | ||
| ...new Set([ | ||
| ...types_1.ALWAYS_INCLUDED_PROVIDERS, | ||
| ...(parseProviderOption(options.providers) ?? (await (0, designSystem_1.promptProviders)())) | ||
| ]) | ||
| ] | ||
| : []; | ||
| const pullResult = await (0, registryClient_1.pullRegistryMarkdown)(parsedSlug.data, format); | ||
| if (!pullResult.ok) { | ||
@@ -90,2 +157,3 @@ throw new Error(`Registry pull failed: ${pullResult.reason}`); | ||
| markdown: pullResult.markdown, | ||
| format, | ||
| dryRun: Boolean(options.dryRun) | ||
@@ -96,2 +164,3 @@ }); | ||
| async function listLike(options) { | ||
| const format = await resolvePullFormatOption(options.format); | ||
| const specsResult = await (0, registryClient_1.listRegistrySpecs)(); | ||
@@ -105,8 +174,8 @@ if (!specsResult.ok) { | ||
| } | ||
| const selectableSpecs = specsResult.specs.filter((spec) => spec.hasSkillMd); | ||
| const selectableSpecs = specsResult.specs.filter((spec) => (format === "skill" ? spec.hasSkillMd : spec.hasDesignMd)); | ||
| if (selectableSpecs.length === 0) { | ||
| throw new Error("No pullable registry specs available."); | ||
| throw new Error(`No pullable registry specs available for format '${format}'.`); | ||
| } | ||
| const selected = await (0, registry_1.promptRegistrySpecSelection)(specsResult.specs); | ||
| await pullLike(selected.slug, options); | ||
| const selected = await (0, registry_1.promptRegistrySpecSelection)(specsResult.specs, format); | ||
| await pullLike(selected.slug, { ...options, format }); | ||
| } | ||
@@ -128,4 +197,5 @@ const program = new commander_1.Command(); | ||
| .command("generate") | ||
| .description("Generate provider skill files in the current project.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers") | ||
| .description("Generate SKILL.md provider files or DESIGN.md in the current project.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers (skill format only)") | ||
| .option("-f, --format <format>", "Output format (skill|design)") | ||
| .option("--dry-run", "Preview file changes without writing") | ||
@@ -137,4 +207,5 @@ .action(async (options) => { | ||
| .command("update") | ||
| .description("Update existing provider skill files in the current project.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers") | ||
| .description("Update existing SKILL.md provider files or root DESIGN.md in the current project.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers (skill format only)") | ||
| .option("-f, --format <format>", "Output format (skill|design)") | ||
| .option("--dry-run", "Preview file changes without writing") | ||
@@ -146,4 +217,5 @@ .action(async (options) => { | ||
| .command("pull <slug>") | ||
| .description("Pull a registry skill by slug and write selected provider files.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers") | ||
| .description("Pull a registry markdown file by slug and write SKILL.md or DESIGN.md outputs.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers (skill format only)") | ||
| .option("-f, --format <format>", "Registry file format (skill|design)") | ||
| .option("--dry-run", "Preview file changes without writing") | ||
@@ -156,3 +228,4 @@ .action(async (slug, options) => { | ||
| .description("List available registry design system specs.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers") | ||
| .option("-p, --providers <providers>", "Comma-separated providers (skill format only)") | ||
| .option("-f, --format <format>", "Registry file format (skill|design)") | ||
| .option("--dry-run", "Preview file changes without writing") | ||
@@ -162,2 +235,11 @@ .action(async (options) => { | ||
| }); | ||
| program | ||
| .command("randomize") | ||
| .description("Generate a randomized local design system and write SKILL.md or DESIGN.md outputs.") | ||
| .option("-p, --providers <providers>", "Comma-separated providers (skill format only)") | ||
| .option("-f, --format <format>", "Output format (skill|design)") | ||
| .option("--dry-run", "Preview file changes without writing") | ||
| .action(async (options) => { | ||
| await randomizeLike(options); | ||
| }); | ||
| program.parseAsync().catch((error) => { | ||
@@ -164,0 +246,0 @@ const message = error instanceof Error ? error.message : String(error); |
+3
-3
@@ -14,4 +14,4 @@ "use strict"; | ||
| exports.REGISTRY_SPECS_URL = `${exports.GITHUB_REGISTRY_RAW_BASE_URL}/skills/index.json`; | ||
| function getRegistryPullUrl(skillPath) { | ||
| const encodedPath = skillPath | ||
| function getRegistryPullUrl(markdownPath) { | ||
| const encodedPath = markdownPath | ||
| .split("/") | ||
@@ -30,3 +30,3 @@ .filter(Boolean) | ||
| function getDesignSystemPreviewUrl(slug) { | ||
| return `${exports.API_DOMAIN}/design-systems/${encodeURIComponent(slug)}`; | ||
| return `${exports.API_DOMAIN}/design-skills/${encodeURIComponent(slug)}`; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.RegistrySlugSchema = exports.FlatDesignSystemPromptSchema = exports.SkillMetadataSchema = exports.DesignSystemSchema = exports.ProviderSelectionSchema = void 0; | ||
| exports.PullFormatSchema = exports.RegistrySlugSchema = exports.FlatDesignSystemPromptSchema = exports.SkillMetadataSchema = exports.DesignSystemSchema = exports.ProviderSelectionSchema = void 0; | ||
| const zod_1 = require("zod"); | ||
@@ -64,1 +64,2 @@ const types_1 = require("../types"); | ||
| .regex(/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/, "Slug must contain only lowercase letters, numbers, dashes, or underscores."); | ||
| exports.PullFormatSchema = zod_1.z.enum(["skill", "design"]); |
@@ -7,4 +7,6 @@ "use strict"; | ||
| exports.parseManagedDesignSystem = parseManagedDesignSystem; | ||
| exports.parseDesignMarkdown = parseDesignMarkdown; | ||
| exports.parseSkillMetadata = parseSkillMetadata; | ||
| exports.loadExistingDesignSystem = loadExistingDesignSystem; | ||
| exports.loadExistingDesignMarkdown = loadExistingDesignMarkdown; | ||
| const promises_1 = __importDefault(require("node:fs/promises")); | ||
@@ -52,2 +54,7 @@ const node_path_1 = __importDefault(require("node:path")); | ||
| } | ||
| function extractSectionToEnd(body, title) { | ||
| const pattern = new RegExp(`${escapeRegExp(title)}\\n([\\s\\S]*)`); | ||
| const match = body.match(pattern); | ||
| return match?.[1]?.trim() ?? null; | ||
| } | ||
| function extractListSection(body, title, nextTitle) { | ||
@@ -67,2 +74,6 @@ const text = extractSection(body, title, nextTitle); | ||
| } | ||
| function extractDesignStyleValue(body, label) { | ||
| const match = body.match(new RegExp(`^- \\*\\*${escapeRegExp(label)}:\\*\\* (.+)$`, "m")); | ||
| return match?.[1]?.trim() ?? null; | ||
| } | ||
| function parseManagedDesignSystem(content) { | ||
@@ -108,2 +119,38 @@ const managed = extractManagedBlock(content); | ||
| } | ||
| function parseDesignMarkdown(content) { | ||
| const frontmatter = extractFrontmatter(content); | ||
| if (!frontmatter) { | ||
| return null; | ||
| } | ||
| const productNameRaw = parseFrontmatterField(frontmatter, "name"); | ||
| const productName = productNameRaw ? parseQuotedYamlValue(productNameRaw) : ""; | ||
| const brandSummary = extractSection(content, "## Overview", "## Style Foundations"); | ||
| const visualStyle = extractDesignStyleValue(content, "Visual style"); | ||
| const typographyScale = extractDesignStyleValue(content, "Typography scale"); | ||
| const colorPalette = extractDesignStyleValue(content, "Color palette"); | ||
| const spacingScale = extractDesignStyleValue(content, "Spacing scale"); | ||
| const accessibilityRequirements = extractSection(content, "## Accessibility", "## Writing Tone"); | ||
| const writingTone = extractSection(content, "## Writing Tone", "## Rules: Do"); | ||
| const doRules = extractListSection(content, "## Rules: Do", "## Rules: Don't"); | ||
| const dontText = extractSectionToEnd(content, "## Rules: Don't"); | ||
| const dontRules = dontText | ||
| ? dontText | ||
| .split("\n") | ||
| .map((line) => line.replace(/^- /, "").trim()) | ||
| .filter(Boolean) | ||
| : null; | ||
| const parsed = designSystemSchema_1.DesignSystemSchema.safeParse({ | ||
| productName, | ||
| brandSummary: brandSummary ?? "", | ||
| visualStyle: visualStyle ?? "", | ||
| typographyScale: typographyScale ?? "", | ||
| colorPalette: colorPalette ?? "", | ||
| spacingScale: spacingScale ?? "", | ||
| accessibilityRequirements: accessibilityRequirements ?? "", | ||
| writingTone: writingTone ?? "", | ||
| doRules: doRules ?? [], | ||
| dontRules: dontRules ?? [] | ||
| }); | ||
| return parsed.success ? parsed.data : null; | ||
| } | ||
| function parseSkillMetadata(content) { | ||
@@ -148,1 +195,15 @@ const frontmatter = extractFrontmatter(content); | ||
| } | ||
| async function loadExistingDesignMarkdown(projectRoot) { | ||
| const absPath = node_path_1.default.resolve(projectRoot, "DESIGN.md"); | ||
| try { | ||
| const content = await promises_1.default.readFile(absPath, "utf8"); | ||
| return parseDesignMarkdown(content); | ||
| } | ||
| catch (error) { | ||
| const e = error; | ||
| if (e.code !== "ENOENT") { | ||
| throw error; | ||
| } | ||
| } | ||
| return null; | ||
| } |
@@ -7,6 +7,20 @@ "use strict"; | ||
| async function runPull(options) { | ||
| const format = options.format ?? "skill"; | ||
| if (format === "design") { | ||
| const result = await (0, updateSkillFile_1.writeMarkdownFile)(options.projectRoot, "DESIGN.md", options.markdown, options.dryRun ?? false); | ||
| return [ | ||
| { | ||
| filePath: result.absPath, | ||
| changed: result.changed | ||
| } | ||
| ]; | ||
| } | ||
| const providers = options.providers ?? []; | ||
| if (providers.length === 0) { | ||
| throw new Error("No providers selected for skill format."); | ||
| } | ||
| const results = []; | ||
| const seenPaths = new Set(); | ||
| for (const provider of options.providers) { | ||
| const relativePath = types_1.PROVIDER_DETAILS[provider].relativePath; | ||
| for (const provider of providers) { | ||
| const relativePath = (0, types_1.getProviderOutputPath)(provider, format); | ||
| if (seenPaths.has(relativePath)) { | ||
@@ -13,0 +27,0 @@ continue; |
@@ -7,2 +7,3 @@ "use strict"; | ||
| exports.upsertManagedSkillFile = upsertManagedSkillFile; | ||
| exports.writeMarkdownFile = writeMarkdownFile; | ||
| const promises_1 = __importDefault(require("node:fs/promises")); | ||
@@ -78,1 +79,21 @@ const node_path_1 = __importDefault(require("node:path")); | ||
| } | ||
| async function writeMarkdownFile(projectRoot, relativePath, content, dryRun = false) { | ||
| const absPath = node_path_1.default.resolve(projectRoot, relativePath); | ||
| await promises_1.default.mkdir(node_path_1.default.dirname(absPath), { recursive: true }); | ||
| let existing = ""; | ||
| try { | ||
| existing = await promises_1.default.readFile(absPath, "utf8"); | ||
| } | ||
| catch (error) { | ||
| const e = error; | ||
| if (e.code !== "ENOENT") { | ||
| throw error; | ||
| } | ||
| } | ||
| const nextContent = content.endsWith("\n") ? content : `${content}\n`; | ||
| const changed = existing !== nextContent; | ||
| if (!dryRun && changed) { | ||
| await promises_1.default.writeFile(absPath, nextContent, "utf8"); | ||
| } | ||
| return { absPath, changed }; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.promptPullFormatSelection = promptPullFormatSelection; | ||
| exports.promptRegistrySpecSelection = promptRegistrySpecSelection; | ||
@@ -14,7 +15,33 @@ const config_1 = require("../config"); | ||
| } | ||
| async function promptRegistrySpecSelection(specs) { | ||
| async function promptPullFormatSelection() { | ||
| const answer = await prompt([ | ||
| { | ||
| type: "list", | ||
| name: "format", | ||
| message: "Select output format:", | ||
| choices: [ | ||
| { | ||
| name: "SKILL.md (provider-specific paths)", | ||
| value: "skill" | ||
| }, | ||
| { | ||
| name: "DESIGN.md (project root)", | ||
| value: "design" | ||
| } | ||
| ] | ||
| } | ||
| ]); | ||
| return answer.format; | ||
| } | ||
| async function promptRegistrySpecSelection(specs, format) { | ||
| const choices = specs.map((spec) => ({ | ||
| name: `${spec.name} (${(0, config_1.getDesignSystemPreviewUrl)(spec.slug).replace(/^https?:\/\//, "")})${spec.hasSkillMd ? "" : " - no skill markdown"}`, | ||
| name: `${spec.name} (${(0, config_1.getDesignSystemPreviewUrl)(spec.slug).replace(/^https?:\/\//, "")})${(format === "skill" ? spec.hasSkillMd : spec.hasDesignMd) ? "" : ` - no ${format} markdown`}`, | ||
| value: spec.slug, | ||
| disabled: spec.hasSkillMd ? false : "No skill markdown available for pull." | ||
| disabled: format === "skill" | ||
| ? spec.hasSkillMd | ||
| ? false | ||
| : "No skill markdown available for pull." | ||
| : spec.hasDesignMd | ||
| ? false | ||
| : "No design markdown available for pull." | ||
| })); | ||
@@ -25,3 +52,3 @@ const answer = await prompt([ | ||
| name: "selected", | ||
| message: "Select one registry spec to pull:", | ||
| message: `Select one registry spec to pull (${format}):`, | ||
| choices, | ||
@@ -28,0 +55,0 @@ validate: (value) => value.length === 1 || "Select exactly one spec." |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.pullRegistryMarkdown = pullRegistryMarkdown; | ||
| exports.pullSkillMarkdown = pullSkillMarkdown; | ||
@@ -26,9 +27,16 @@ exports.listRegistrySpecs = listRegistrySpecs; | ||
| const candidate = rawValue; | ||
| if (typeof candidate.slug !== "string" || typeof candidate.name !== "string" || typeof candidate.skillPath !== "string") { | ||
| if (typeof candidate.slug !== "string" || typeof candidate.name !== "string") { | ||
| return null; | ||
| } | ||
| if (candidate.skillPath !== undefined && typeof candidate.skillPath !== "string") { | ||
| return null; | ||
| } | ||
| if (candidate.designPath !== undefined && typeof candidate.designPath !== "string") { | ||
| return null; | ||
| } | ||
| index[key] = { | ||
| slug: candidate.slug, | ||
| name: candidate.name, | ||
| skillPath: candidate.skillPath | ||
| skillPath: candidate.skillPath ?? "", | ||
| designPath: candidate.designPath ?? "" | ||
| }; | ||
@@ -84,3 +92,20 @@ } | ||
| } | ||
| async function pullSkillMarkdown(slug) { | ||
| function inferDesignPathFromSkillPath(skillPath) { | ||
| const normalizedSkillPath = skillPath.trim(); | ||
| if (!normalizedSkillPath.endsWith("SKILL.md")) { | ||
| return ""; | ||
| } | ||
| return normalizedSkillPath.replace(/SKILL\.md$/, "DESIGN.md"); | ||
| } | ||
| function resolveRegistryMarkdownPath(entry, format) { | ||
| if (format === "skill") { | ||
| return entry.skillPath.trim(); | ||
| } | ||
| const explicitDesignPath = entry.designPath.trim(); | ||
| if (explicitDesignPath) { | ||
| return explicitDesignPath; | ||
| } | ||
| return inferDesignPathFromSkillPath(entry.skillPath); | ||
| } | ||
| async function pullRegistryMarkdown(slug, format) { | ||
| const parsedSlug = designSystemSchema_1.RegistrySlugSchema.safeParse(slug); | ||
@@ -104,9 +129,10 @@ if (!parsedSlug.success) { | ||
| } | ||
| if (!entry.skillPath.trim()) { | ||
| const markdownPath = resolveRegistryMarkdownPath(entry, format); | ||
| if (!markdownPath) { | ||
| return { | ||
| ok: false, | ||
| reason: `No skill markdown path found for slug '${parsedSlug.data}'.` | ||
| reason: `No ${format} markdown path found for slug '${parsedSlug.data}'.` | ||
| }; | ||
| } | ||
| const endpoint = (0, config_1.getRegistryPullUrl)(entry.skillPath); | ||
| const endpoint = (0, config_1.getRegistryPullUrl)(markdownPath); | ||
| let response; | ||
@@ -131,3 +157,3 @@ try { | ||
| ok: false, | ||
| reason: mapRegistryHttpFailure(response.status, `fetching markdown for '${parsedSlug.data}'`) | ||
| reason: mapRegistryHttpFailure(response.status, `fetching ${format} markdown for '${parsedSlug.data}'`) | ||
| }; | ||
@@ -152,2 +178,5 @@ } | ||
| } | ||
| async function pullSkillMarkdown(slug) { | ||
| return pullRegistryMarkdown(slug, "skill"); | ||
| } | ||
| async function listRegistrySpecs() { | ||
@@ -163,3 +192,4 @@ const indexResult = await fetchRegistryIndex(); | ||
| previewUrl: (0, config_1.getDesignSystemUrl)(entry.slug), | ||
| hasSkillMd: Boolean(entry.skillPath.trim()) | ||
| hasSkillMd: Boolean(entry.skillPath.trim()), | ||
| hasDesignMd: Boolean(resolveRegistryMarkdownPath(entry, "design")) | ||
| })); | ||
@@ -166,0 +196,0 @@ return { |
+124
-0
@@ -6,2 +6,3 @@ "use strict"; | ||
| exports.createManagedSkillFile = createManagedSkillFile; | ||
| exports.createDesignMarkdownFile = createDesignMarkdownFile; | ||
| const config_1 = require("../config"); | ||
@@ -89,2 +90,65 @@ const skillMetadata_1 = require("../skillMetadata"); | ||
| } | ||
| function parseKeyValuePairs(value) { | ||
| return value | ||
| .split(",") | ||
| .map((part) => part.trim()) | ||
| .filter(Boolean) | ||
| .reduce((acc, part) => { | ||
| const [rawKey, ...rawValueParts] = part.split("="); | ||
| const key = rawKey?.trim().toLowerCase(); | ||
| const rawValue = rawValueParts.join("=").trim(); | ||
| if (key && rawValue) { | ||
| acc[key] = rawValue; | ||
| } | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| function parseTypographyMetadata(typographyScale) { | ||
| const sourceScale = typographyScale.split("|")[0]?.trim() || "12/14/16/20/24/32"; | ||
| const fontsMatch = typographyScale.match(/\|\s*Fonts:\s*([^|]+)/i); | ||
| const fontPairs = parseKeyValuePairs(fontsMatch?.[1] ?? ""); | ||
| const weightsMatch = typographyScale.match(/weights\s*=\s*([^|]+)/i); | ||
| return { | ||
| sourceScale, | ||
| primary: fontPairs.primary ?? "Public Sans", | ||
| display: fontPairs.display ?? fontPairs.primary ?? "Public Sans", | ||
| mono: fontPairs.mono ?? "Space Grotesk", | ||
| weights: weightsMatch?.[1]?.trim() ?? "400, 500, 600, 700" | ||
| }; | ||
| } | ||
| function parseColorTokens(colorPalette) { | ||
| const tokensMatch = colorPalette.match(/\|\s*Tokens:\s*([^|]+)/i); | ||
| const tokenPairs = parseKeyValuePairs(tokensMatch?.[1] ?? ""); | ||
| const primary = tokenPairs.primary ?? "#1A1C1E"; | ||
| const secondary = tokenPairs.secondary ?? "#6C7278"; | ||
| const surface = tokenPairs.surface ?? "#F7F5F2"; | ||
| const text = tokenPairs.text ?? "#1A1C1E"; | ||
| return { | ||
| primary, | ||
| secondary, | ||
| tertiary: tokenPairs.tertiary ?? secondary, | ||
| neutral: tokenPairs.neutral ?? surface, | ||
| success: tokenPairs.success ?? "#16A34A", | ||
| warning: tokenPairs.warning ?? "#D97706", | ||
| danger: tokenPairs.danger ?? "#DC2626", | ||
| surface, | ||
| text | ||
| }; | ||
| } | ||
| function deriveSpacingTokens(spacingScale) { | ||
| const numericValues = spacingScale.match(/\d+/g)?.map((value) => Number(value)) ?? []; | ||
| if (numericValues.length >= 2) { | ||
| return { | ||
| sm: `${numericValues[0]}px`, | ||
| md: `${numericValues[1]}px` | ||
| }; | ||
| } | ||
| if (/compact/i.test(spacingScale)) { | ||
| return { sm: "4px", md: "8px" }; | ||
| } | ||
| if (/comfortable/i.test(spacingScale)) { | ||
| return { sm: "8px", md: "16px" }; | ||
| } | ||
| return { sm: "8px", md: "16px" }; | ||
| } | ||
| function createSkillFrontmatter(metadata) { | ||
@@ -103,1 +167,61 @@ return [ | ||
| } | ||
| function createDesignMarkdownFile(design) { | ||
| const typography = parseTypographyMetadata(design.typographyScale); | ||
| const colors = parseColorTokens(design.colorPalette); | ||
| const spacing = deriveSpacingTokens(design.spacingScale); | ||
| return [ | ||
| "---", | ||
| `name: "${escapeYamlString(design.productName)}"`, | ||
| "colors:", | ||
| ` primary: "${colors.primary}"`, | ||
| ` secondary: "${colors.secondary}"`, | ||
| ` tertiary: "${colors.tertiary}"`, | ||
| ` neutral: "${colors.neutral}"`, | ||
| ` success: "${colors.success}"`, | ||
| ` warning: "${colors.warning}"`, | ||
| ` danger: "${colors.danger}"`, | ||
| ` surface: "${colors.surface}"`, | ||
| ` text: "${colors.text}"`, | ||
| "typography:", | ||
| " h1:", | ||
| ` fontFamily: "${escapeYamlString(typography.display)}"`, | ||
| " fontSize: 3rem", | ||
| " body-md:", | ||
| ` fontFamily: "${escapeYamlString(typography.primary)}"`, | ||
| " fontSize: 1rem", | ||
| " label-caps:", | ||
| ` fontFamily: "${escapeYamlString(typography.mono)}"`, | ||
| " fontSize: 0.75rem", | ||
| ` sourceScale: "${escapeYamlString(typography.sourceScale)}"`, | ||
| ` weights: "${escapeYamlString(typography.weights)}"`, | ||
| "rounded:", | ||
| " sm: 4px", | ||
| " md: 8px", | ||
| "spacing:", | ||
| ` sm: ${spacing.sm}`, | ||
| ` md: ${spacing.md}`, | ||
| ` sourceScale: "${escapeYamlString(design.spacingScale)}"`, | ||
| "---", | ||
| "", | ||
| "## Overview", | ||
| design.brandSummary, | ||
| "", | ||
| "## Style Foundations", | ||
| `- **Visual style:** ${design.visualStyle}`, | ||
| `- **Typography scale:** ${design.typographyScale}`, | ||
| `- **Color palette:** ${design.colorPalette}`, | ||
| `- **Spacing scale:** ${design.spacingScale}`, | ||
| "", | ||
| "## Accessibility", | ||
| design.accessibilityRequirements, | ||
| "", | ||
| "## Writing Tone", | ||
| design.writingTone, | ||
| "", | ||
| "## Rules: Do", | ||
| list(design.doRules), | ||
| "", | ||
| "## Rules: Don't", | ||
| list(design.dontRules) | ||
| ].join("\n"); | ||
| } |
+8
-0
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.DESIGN_SYSTEM_FIELDS = exports.OPTIONAL_PROVIDERS = exports.ALWAYS_INCLUDED_PROVIDERS = exports.SUPPORTED_PROVIDERS = exports.PROVIDER_DETAILS = void 0; | ||
| exports.getProviderOutputPath = getProviderOutputPath; | ||
| exports.PROVIDER_DETAILS = { | ||
@@ -150,2 +151,9 @@ universal: { | ||
| exports.OPTIONAL_PROVIDERS = exports.SUPPORTED_PROVIDERS.filter((provider) => !Boolean(exports.PROVIDER_DETAILS[provider].alwaysIncluded)); | ||
| function getProviderOutputPath(provider, format) { | ||
| const skillPath = exports.PROVIDER_DETAILS[provider].relativePath; | ||
| if (format === "skill") { | ||
| return skillPath; | ||
| } | ||
| return skillPath.replace(/\/SKILL\.md$/, "/DESIGN.md"); | ||
| } | ||
| exports.DESIGN_SYSTEM_FIELDS = [ | ||
@@ -152,0 +160,0 @@ "productName", |
+1
-1
| { | ||
| "name": "typeui.sh", | ||
| "version": "0.6.0", | ||
| "version": "0.7.0", | ||
| "description": "Generate design system specifications and style guides as skill files for AI coding providers", | ||
@@ -5,0 +5,0 @@ "main": "dist/cli.js", |
+26
-10
@@ -12,3 +12,3 @@ ``` | ||
| [typeui.sh](https://www.typeui.sh) is an open-source command line interface (CLI) that generates, updates, and can download skill.md files with design system specifications to instruct agentic tools and LLM's to use a certain design when building interfaces. | ||
| [TypeUI](https://www.typeui.sh) is an open-source command line interface (CLI) that generates, updates, and can download `SKILL.md` or `DESIGN.md` files with design system specifications to instruct agentic tools and LLM's to use a certain design when building interfaces. | ||
@@ -23,5 +23,5 @@ ## Getting started | ||
| ## Design systems | ||
| ## Design skills | ||
| Check out all [design systems](https://typeui.sh/design-skills) that can be pulled into your project. | ||
| Check out all [design skills](https://wwww.typeui.sh/design-skills) that can be pulled into your project. Available in both `DESIGN.md` and `SKILL.md` formats. | ||
@@ -32,6 +32,7 @@ ## Available commands | ||
| | --- | --- | | ||
| | `generate` | Run the interactive design system prompts and generate skill files. | | ||
| | `update` | Update existing managed skill content in generated files. | | ||
| | `pull <slug>` | Pull a registry skill from `bergside/awesome-design-skills` and write it to selected provider paths. | | ||
| | `generate` | Run interactive prompts, choose `SKILL.md` or `DESIGN.md`, then generate output files. | | ||
| | `update` | Run interactive prompts, choose `SKILL.md` or `DESIGN.md`, then update existing output files. | | ||
| | `pull <slug>` | Pull a registry markdown file from `bergside/awesome-design-skills` (`SKILL.md` -> provider paths, `DESIGN.md` -> project root `DESIGN.md`). | | ||
| | `list` | Show available registry specs from `bergside/awesome-design-skills` (with typeui.sh preview links), then pull one automatically. | | ||
| | `randomize` | Generate a fully randomized local design system and write `SKILL.md` or `DESIGN.md` outputs. | | ||
@@ -58,8 +59,23 @@ ## Design Skill File Structure | ||
| For local development: | ||
| ## Local development | ||
| If you want to use this locally these are the commands you need to run: | ||
| ```bash | ||
| npm install | ||
| npm run build | ||
| ``` | ||
| Then use the commands in your terminal: | ||
| ``` | ||
| node dist/cli.js --help | ||
| node dist/cli.js generate | ||
| node dist/cli.js generate --format design | ||
| node dist/cli.js randomize | ||
| node dist/cli.js randomize --format design | ||
| node dist/cli.js list | ||
| node dist/cli.js pull [slug] | ||
| node dist/cli.js pull [slug] --format design | ||
| node dist/cli.js list --format design | ||
| ``` | ||
@@ -69,10 +85,10 @@ | ||
| The CLI is open-source under the MIT License. | ||
| The CLI and public registry is open-source under the MIT License. | ||
| ## Pro version | ||
| Get access to curated design system files by getting the [pro version](https://www.typeui.sh/#pricing) and supporting our work. | ||
| Get access to enhanched design skill files and a private Discord community by getting the [pro version](https://www.typeui.sh/#pricing) and thus supporting our open-source work. | ||
| ## Sponsors | ||
| If you'd like to become a sponsor of the project, please [contact us](https://www.bergside.com/contact) on our company website. | ||
| If you'd like to become a sponsor of the project, please check out the [sponsorship page](https://www.typeui.sh/sponsor) on our website. |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
95085
34.52%24
9.09%2407
40.6%90
21.62%