| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.SKILL_AUTHOR = void 0; | ||
| exports.slugifySkillName = slugifySkillName; | ||
| exports.buildDefaultSkillMetadata = buildDefaultSkillMetadata; | ||
| const designSystemSchema_1 = require("./domain/designSystemSchema"); | ||
| exports.SKILL_AUTHOR = "typeui.sh"; | ||
| function slugifySkillName(value) { | ||
| const slug = value | ||
| .toLowerCase() | ||
| .replace(/[^a-z0-9]+/g, "-") | ||
| .replace(/^-+|-+$/g, ""); | ||
| return slug || "design-system"; | ||
| } | ||
| function buildDefaultSkillMetadata(productName) { | ||
| return designSystemSchema_1.SkillMetadataSchema.parse({ | ||
| name: slugifySkillName(productName), | ||
| description: `${productName} style guide for AI coding agents.` | ||
| }); | ||
| } |
+11
-27
@@ -9,3 +9,2 @@ #!/usr/bin/env node | ||
| const commander_1 = require("commander"); | ||
| const licenseService_1 = require("./licensing/licenseService"); | ||
| const designSystem_1 = require("./prompts/designSystem"); | ||
@@ -20,2 +19,3 @@ const registry_1 = require("./prompts/registry"); | ||
| const banner_1 = require("./ui/banner"); | ||
| const skillMetadata_1 = require("./skillMetadata"); | ||
| function parseProviderOption(raw) { | ||
@@ -45,7 +45,8 @@ if (!raw) { | ||
| } | ||
| async function generateLike(mode, options) { | ||
| async function generateLike(action, mode, options) { | ||
| const selectedProviders = parseProviderOption(options.providers) ?? (await (0, designSystem_1.promptProviders)()); | ||
| const providers = [...new Set([...types_1.ALWAYS_INCLUDED_PROVIDERS, ...selectedProviders])]; | ||
| let designSystem; | ||
| if (mode === "updated") { | ||
| let metadata = (0, skillMetadata_1.buildDefaultSkillMetadata)("typeui.sh"); | ||
| if (action === "update") { | ||
| const existing = await (0, existingDesignSystem_1.loadExistingDesignSystem)(process.cwd(), providers); | ||
@@ -55,8 +56,10 @@ if (!existing) { | ||
| } | ||
| metadata = await (0, designSystem_1.promptSkillMetadata)(existing.metadata); | ||
| const fields = await (0, designSystem_1.promptDesignSystemFields)(); | ||
| const updates = await (0, designSystem_1.promptDesignSystemUpdates)(existing, fields); | ||
| designSystem = { ...existing, ...updates }; | ||
| const updates = await (0, designSystem_1.promptDesignSystemUpdates)(existing.designSystem, fields); | ||
| designSystem = { ...existing.designSystem, ...updates }; | ||
| } | ||
| else { | ||
| designSystem = await (0, designSystem_1.promptDesignSystem)("typeui.sh"); | ||
| metadata = await (0, designSystem_1.promptSkillMetadata)((0, skillMetadata_1.buildDefaultSkillMetadata)(designSystem.productName)); | ||
| } | ||
@@ -67,2 +70,3 @@ const results = await (0, runGeneration_1.runGeneration)({ | ||
| designSystem, | ||
| metadata, | ||
| dryRun: Boolean(options.dryRun) | ||
@@ -126,3 +130,3 @@ }); | ||
| .action(async (options) => { | ||
| await generateLike(options.dryRun ? "preview" : "generated", options); | ||
| await generateLike("generate", options.dryRun ? "preview" : "generated", options); | ||
| }); | ||
@@ -135,3 +139,3 @@ program | ||
| .action(async (options) => { | ||
| await generateLike(options.dryRun ? "preview" : "updated", options); | ||
| await generateLike("update", options.dryRun ? "preview" : "updated", options); | ||
| }); | ||
@@ -154,22 +158,2 @@ program | ||
| }); | ||
| program | ||
| .command("verify") | ||
| .description("Verify your license key and cache local license status.") | ||
| .action(async () => { | ||
| const record = await (0, licenseService_1.verifyAndCacheLicenseFromPrompt)(); | ||
| console.log(`License cached (${record.licenseKeyFingerprint}) until ${record.expiresAt}`); | ||
| }); | ||
| program | ||
| .command("license") | ||
| .description("Show local cached license status.") | ||
| .action(async () => { | ||
| console.log(await (0, licenseService_1.getCachedLicenseSummary)()); | ||
| }); | ||
| program | ||
| .command("clear-cache") | ||
| .description("Clear all local typeui.sh cache state.") | ||
| .action(async () => { | ||
| await (0, licenseService_1.clearCachedLicenseState)(); | ||
| console.log("Cleared local cache state."); | ||
| }); | ||
| program.parseAsync().catch((error) => { | ||
@@ -176,0 +160,0 @@ const message = error instanceof Error ? error.message : String(error); |
+1
-15
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.LICENSE_CACHE_PATH = exports.LICENSE_CACHE_DIR = exports.REGISTRY_SPECS_URL = exports.POLAR_VERIFY_URL = exports.PRICING_URL = exports.GITHUB_REGISTRY_RAW_BASE_URL = exports.GITHUB_REGISTRY_REPO_URL = exports.API_DOMAIN = exports.MANAGED_BLOCK_END = exports.MANAGED_BLOCK_START = exports.PRODUCT_ID = void 0; | ||
| exports.getPolarVerifyUrl = getPolarVerifyUrl; | ||
| exports.REGISTRY_SPECS_URL = exports.GITHUB_REGISTRY_RAW_BASE_URL = exports.GITHUB_REGISTRY_REPO_URL = exports.API_DOMAIN = exports.MANAGED_BLOCK_END = exports.MANAGED_BLOCK_START = void 0; | ||
| exports.getRegistryPullUrl = getRegistryPullUrl; | ||
@@ -12,5 +8,2 @@ exports.getRegistrySpecsUrl = getRegistrySpecsUrl; | ||
| exports.getDesignSystemPreviewUrl = getDesignSystemPreviewUrl; | ||
| const node_path_1 = __importDefault(require("node:path")); | ||
| const node_os_1 = __importDefault(require("node:os")); | ||
| exports.PRODUCT_ID = "typeui.sh"; | ||
| exports.MANAGED_BLOCK_START = "<!-- TYPEUI_SH_MANAGED_START -->"; | ||
@@ -21,10 +14,3 @@ exports.MANAGED_BLOCK_END = "<!-- TYPEUI_SH_MANAGED_END -->"; | ||
| exports.GITHUB_REGISTRY_RAW_BASE_URL = "https://raw.githubusercontent.com/bergside/awesome-design-skills/main"; | ||
| exports.PRICING_URL = "https://www.typeui.sh/#pricing"; | ||
| exports.POLAR_VERIFY_URL = `${exports.API_DOMAIN}/api/license/verify`; | ||
| exports.REGISTRY_SPECS_URL = `${exports.GITHUB_REGISTRY_RAW_BASE_URL}/skills/index.json`; | ||
| exports.LICENSE_CACHE_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".typeui-sh"); | ||
| exports.LICENSE_CACHE_PATH = node_path_1.default.join(exports.LICENSE_CACHE_DIR, "license.json"); | ||
| function getPolarVerifyUrl() { | ||
| return exports.POLAR_VERIFY_URL; | ||
| } | ||
| function getRegistryPullUrl(skillPath) { | ||
@@ -31,0 +17,0 @@ const encodedPath = skillPath |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.RegistrySlugSchema = exports.FlatDesignSystemPromptSchema = exports.DesignSystemSchema = exports.ProviderSelectionSchema = void 0; | ||
| exports.RegistrySlugSchema = exports.FlatDesignSystemPromptSchema = exports.SkillMetadataSchema = exports.DesignSystemSchema = exports.ProviderSelectionSchema = void 0; | ||
| const zod_1 = require("zod"); | ||
@@ -27,3 +27,2 @@ const types_1 = require("../types"); | ||
| spacingScale: zod_1.z.string().min(3), | ||
| componentFamilies: zod_1.z.array(zod_1.z.string().min(1)).min(1), | ||
| accessibilityRequirements: zod_1.z.string().min(3), | ||
@@ -34,2 +33,16 @@ writingTone: zod_1.z.string().min(3), | ||
| }); | ||
| exports.SkillMetadataSchema = zod_1.z.object({ | ||
| name: zod_1.z | ||
| .string() | ||
| .trim() | ||
| .min(1, "Skill name is required.") | ||
| .max(100, "Skill name is too long.") | ||
| .regex(/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/, "Skill name must contain only lowercase letters, numbers, dashes, or underscores."), | ||
| description: zod_1.z | ||
| .string() | ||
| .trim() | ||
| .min(3, "Skill description is too short.") | ||
| .max(240, "Skill description is too long.") | ||
| .refine((value) => !/[\r\n]/.test(value), "Skill description must be a single line.") | ||
| }); | ||
| exports.FlatDesignSystemPromptSchema = zod_1.z.object({ | ||
@@ -42,3 +55,2 @@ productName: zod_1.z.string().min(2), | ||
| spacingScale: zod_1.z.string().min(3), | ||
| componentFamilies: nonEmptyList, | ||
| accessibilityRequirements: zod_1.z.string().min(3), | ||
@@ -45,0 +57,0 @@ writingTone: zod_1.z.string().min(3), |
@@ -7,2 +7,3 @@ "use strict"; | ||
| exports.parseManagedDesignSystem = parseManagedDesignSystem; | ||
| exports.parseSkillMetadata = parseSkillMetadata; | ||
| exports.loadExistingDesignSystem = loadExistingDesignSystem; | ||
@@ -13,2 +14,3 @@ const promises_1 = __importDefault(require("node:fs/promises")); | ||
| const designSystemSchema_1 = require("../domain/designSystemSchema"); | ||
| const skillMetadata_1 = require("../skillMetadata"); | ||
| const types_1 = require("../types"); | ||
@@ -26,2 +28,22 @@ function escapeRegExp(value) { | ||
| } | ||
| function extractFrontmatter(content) { | ||
| const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); | ||
| return match?.[1] ?? null; | ||
| } | ||
| function parseFrontmatterField(frontmatter, key) { | ||
| const match = frontmatter.match(new RegExp(`^${escapeRegExp(key)}:\\s*(.+)$`, "m")); | ||
| return match?.[1]?.trim(); | ||
| } | ||
| function parseQuotedYamlValue(value) { | ||
| const trimmed = value.trim(); | ||
| if (trimmed.startsWith('"') && trimmed.endsWith('"')) { | ||
| return trimmed | ||
| .slice(1, -1) | ||
| .replace(/\\(["\\])/g, "$1"); | ||
| } | ||
| if (trimmed.startsWith("'") && trimmed.endsWith("'")) { | ||
| return trimmed.slice(1, -1).replace(/''/g, "'"); | ||
| } | ||
| return trimmed; | ||
| } | ||
| function extractSection(body, title, nextTitle) { | ||
@@ -57,3 +79,2 @@ const pattern = new RegExp(`${escapeRegExp(title)}\\n([\\s\\S]*?)\\n${escapeRegExp(nextTitle)}`, "m"); | ||
| const spacingScale = extractStyleValue(managed, "Spacing scale"); | ||
| const componentFamilies = extractListSection(managed, "## Component Families", "## Accessibility"); | ||
| const accessibilityRequirements = extractSection(managed, "## Accessibility", "## Writing Tone"); | ||
@@ -64,3 +85,3 @@ const writingTone = extractSection(managed, "## Writing Tone", "## Rules: Do"); | ||
| if (!productName || | ||
| !brandSummary || | ||
| brandSummary === null || | ||
| !visualStyle || | ||
@@ -70,3 +91,2 @@ !typographyScale || | ||
| !spacingScale || | ||
| !componentFamilies || | ||
| !accessibilityRequirements || | ||
@@ -85,3 +105,2 @@ !writingTone || | ||
| spacingScale, | ||
| componentFamilies, | ||
| accessibilityRequirements, | ||
@@ -93,2 +112,18 @@ writingTone, | ||
| } | ||
| function parseSkillMetadata(content) { | ||
| const frontmatter = extractFrontmatter(content); | ||
| if (!frontmatter) { | ||
| return null; | ||
| } | ||
| const name = parseFrontmatterField(frontmatter, "name"); | ||
| const description = parseFrontmatterField(frontmatter, "description"); | ||
| if (!name || !description) { | ||
| return null; | ||
| } | ||
| const parsed = designSystemSchema_1.SkillMetadataSchema.safeParse({ | ||
| name: parseQuotedYamlValue(name), | ||
| description: parseQuotedYamlValue(description) | ||
| }); | ||
| return parsed.success ? parsed.data : null; | ||
| } | ||
| async function loadExistingDesignSystem(projectRoot, providers) { | ||
@@ -101,3 +136,7 @@ for (const provider of providers) { | ||
| if (parsed) { | ||
| return parsed; | ||
| const metadata = parseSkillMetadata(content) ?? (0, skillMetadata_1.buildDefaultSkillMetadata)(parsed.productName); | ||
| return { | ||
| designSystem: parsed, | ||
| metadata | ||
| }; | ||
| } | ||
@@ -104,0 +143,0 @@ } |
@@ -7,3 +7,3 @@ "use strict"; | ||
| async function runGeneration(options) { | ||
| const providerFiles = (0, renderers_1.renderProviderFiles)(options.designSystem, options.providers); | ||
| const providerFiles = (0, renderers_1.renderProviderFiles)(options.designSystem, options.providers, options.metadata); | ||
| const results = []; | ||
@@ -10,0 +10,0 @@ for (const file of providerFiles) { |
@@ -10,2 +10,29 @@ "use strict"; | ||
| const config_1 = require("../config"); | ||
| function extractManagedBlock(content) { | ||
| const startIdx = content.indexOf(config_1.MANAGED_BLOCK_START); | ||
| const endIdx = content.indexOf(config_1.MANAGED_BLOCK_END); | ||
| if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) { | ||
| return null; | ||
| } | ||
| return content.slice(startIdx, endIdx + config_1.MANAGED_BLOCK_END.length); | ||
| } | ||
| function extractFrontmatter(content) { | ||
| const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/); | ||
| return match?.[0] ?? null; | ||
| } | ||
| function removeLeadingFrontmatter(content) { | ||
| const frontmatter = extractFrontmatter(content); | ||
| if (!frontmatter) { | ||
| return content; | ||
| } | ||
| return content.slice(frontmatter.length); | ||
| } | ||
| function applyFrontmatter(content, frontmatter) { | ||
| const withoutFrontmatter = removeLeadingFrontmatter(content).trimStart(); | ||
| const normalizedFrontmatter = frontmatter.trimEnd(); | ||
| if (!withoutFrontmatter) { | ||
| return `${normalizedFrontmatter}\n`; | ||
| } | ||
| return `${normalizedFrontmatter}\n\n${withoutFrontmatter}`; | ||
| } | ||
| function mergeWithManagedBlock(existing, generatedBlock) { | ||
@@ -26,3 +53,3 @@ const startIdx = existing.indexOf(config_1.MANAGED_BLOCK_START); | ||
| } | ||
| async function upsertManagedSkillFile(projectRoot, relativePath, generatedBlock, dryRun = false) { | ||
| async function upsertManagedSkillFile(projectRoot, relativePath, generatedContent, dryRun = false) { | ||
| const absPath = node_path_1.default.resolve(projectRoot, relativePath); | ||
@@ -40,3 +67,8 @@ await promises_1.default.mkdir(node_path_1.default.dirname(absPath), { recursive: true }); | ||
| } | ||
| const nextContent = mergeWithManagedBlock(existing, generatedBlock); | ||
| const generatedBlock = extractManagedBlock(generatedContent) ?? generatedContent; | ||
| const generatedFrontmatter = extractFrontmatter(generatedContent); | ||
| let nextContent = mergeWithManagedBlock(existing, generatedBlock); | ||
| if (generatedFrontmatter) { | ||
| nextContent = applyFrontmatter(nextContent, generatedFrontmatter); | ||
| } | ||
| const changed = existing !== nextContent; | ||
@@ -43,0 +75,0 @@ if (!dryRun && changed) { |
@@ -7,2 +7,3 @@ "use strict"; | ||
| exports.promptDesignSystemUpdates = promptDesignSystemUpdates; | ||
| exports.promptSkillMetadata = promptSkillMetadata; | ||
| exports.listSupportedProviders = listSupportedProviders; | ||
@@ -41,3 +42,2 @@ const designSystemSchema_1 = require("../domain/designSystemSchema"); | ||
| { name: "Spacing scale", value: "spacingScale" }, | ||
| { name: "Component families", value: "componentFamilies" }, | ||
| { name: "Accessibility requirements", value: "accessibilityRequirements" }, | ||
@@ -287,47 +287,2 @@ { name: "Writing tone", value: "writingTone" }, | ||
| ]; | ||
| const COMPONENT_FAMILY_OPTIONS = [ | ||
| "buttons", | ||
| "inputs", | ||
| "forms", | ||
| "selects/comboboxes", | ||
| "checkboxes/radios/switches", | ||
| "textareas", | ||
| "date/time pickers", | ||
| "file uploaders", | ||
| "cards", | ||
| "tables", | ||
| "data lists", | ||
| "data grids", | ||
| "charts", | ||
| "stats/metrics", | ||
| "badges/chips", | ||
| "avatars", | ||
| "breadcrumbs", | ||
| "pagination", | ||
| "steppers", | ||
| "modals", | ||
| "drawers/sheets", | ||
| "tooltips", | ||
| "popovers/menus", | ||
| "navigation", | ||
| "sidebars", | ||
| "top bars/headers", | ||
| "command palette", | ||
| "tabs", | ||
| "accordions", | ||
| "carousels", | ||
| "progress indicators", | ||
| "skeletons", | ||
| "alerts/toasts", | ||
| "notifications center", | ||
| "search", | ||
| "empty states", | ||
| "onboarding", | ||
| "authentication screens", | ||
| "settings pages", | ||
| "documentation layouts", | ||
| "feedback components", | ||
| "pricing blocks", | ||
| "data visualization wrappers" | ||
| ]; | ||
| const ACCESSIBILITY_OPTIONS = [ | ||
@@ -512,7 +467,2 @@ "WCAG 2.2 AA", | ||
| }); | ||
| const componentFamilies = await promptPresetSelection({ | ||
| message: "Select component families to prioritize:", | ||
| presets: COMPONENT_FAMILY_OPTIONS, | ||
| defaultSelected: COMPONENT_FAMILY_OPTIONS | ||
| }); | ||
| const accessibilityRequirements = await promptPresetSelection({ | ||
@@ -553,3 +503,2 @@ message: "Select accessibility requirements:", | ||
| spacingScale, | ||
| componentFamilies, | ||
| accessibilityRequirements: accessibilityRequirements.join(", "), | ||
@@ -566,5 +515,4 @@ writingTone: writingTone.join(", "), | ||
| name: "fields", | ||
| message: "Select fields to update:", | ||
| choices: designFieldChoices, | ||
| validate: (value) => value.length > 0 || "Select at least one field." | ||
| message: "Select design fields to update (optional):", | ||
| choices: designFieldChoices | ||
| } | ||
@@ -631,12 +579,2 @@ ]); | ||
| } | ||
| case "componentFamilies": { | ||
| const defaults = matchPresetDefaults(current.componentFamilies.join(", "), COMPONENT_FAMILY_OPTIONS); | ||
| updates.componentFamilies = await promptPresetSelection({ | ||
| message: "Select component families to prioritize:", | ||
| presets: COMPONENT_FAMILY_OPTIONS, | ||
| defaultSelected: defaults.selected.length > 0 ? defaults.selected : COMPONENT_FAMILY_OPTIONS, | ||
| defaultCustom: defaults.custom | ||
| }); | ||
| break; | ||
| } | ||
| case "accessibilityRequirements": { | ||
@@ -693,4 +631,28 @@ const defaults = matchPresetDefaults(current.accessibilityRequirements, ACCESSIBILITY_OPTIONS); | ||
| } | ||
| async function promptSkillMetadata(defaults) { | ||
| const answers = await prompt([ | ||
| { | ||
| type: "input", | ||
| name: "name", | ||
| message: "Skill name (slug, e.g. next-best-practices):", | ||
| default: defaults?.name ?? "design-system", | ||
| validate: (value) => designSystemSchema_1.SkillMetadataSchema.pick({ name: true }).safeParse({ name: value }).success || | ||
| "Use lowercase letters, numbers, dashes, or underscores." | ||
| }, | ||
| { | ||
| type: "input", | ||
| name: "description", | ||
| message: "Skill description (single line):", | ||
| default: defaults?.description ?? "", | ||
| validate: (value) => designSystemSchema_1.SkillMetadataSchema.pick({ description: true }).safeParse({ description: value }).success || | ||
| "Description must be a single non-empty line." | ||
| } | ||
| ]); | ||
| return designSystemSchema_1.SkillMetadataSchema.parse({ | ||
| name: answers.name, | ||
| description: answers.description | ||
| }); | ||
| } | ||
| function listSupportedProviders() { | ||
| return types_1.SUPPORTED_PROVIDERS; | ||
| } |
@@ -6,8 +6,8 @@ "use strict"; | ||
| const shared_1 = require("./shared"); | ||
| function renderProviderFiles(design, providers) { | ||
| function renderProviderFiles(design, providers, metadata) { | ||
| return providers.map((provider) => ({ | ||
| provider, | ||
| relativePath: types_1.PROVIDER_DETAILS[provider].relativePath, | ||
| content: (0, shared_1.createManagedSkillBody)(types_1.PROVIDER_DETAILS[provider].title, design) | ||
| content: (0, shared_1.createManagedSkillFile)(types_1.PROVIDER_DETAILS[provider].title, design, metadata) | ||
| })); | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.createManagedSkillBody = createManagedSkillBody; | ||
| exports.createSkillFrontmatter = createSkillFrontmatter; | ||
| exports.createManagedSkillFile = createManagedSkillFile; | ||
| const config_1 = require("../config"); | ||
| const skillMetadata_1 = require("../skillMetadata"); | ||
| function list(items) { | ||
@@ -26,5 +29,2 @@ return items.map((item) => `- ${item}`).join("\n"); | ||
| "", | ||
| "## Component Families", | ||
| list(design.componentFamilies), | ||
| "", | ||
| "## Accessibility", | ||
@@ -86,1 +86,17 @@ design.accessibilityRequirements, | ||
| } | ||
| function escapeYamlString(value) { | ||
| return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); | ||
| } | ||
| function createSkillFrontmatter(metadata) { | ||
| return [ | ||
| "---", | ||
| `name: "${escapeYamlString(metadata.name)}"`, | ||
| `description: "${escapeYamlString(metadata.description)}"`, | ||
| "metadata:", | ||
| ` author: ${escapeYamlString(skillMetadata_1.SKILL_AUTHOR)}`, | ||
| "---" | ||
| ].join("\n"); | ||
| } | ||
| function createManagedSkillFile(providerTitle, design, metadata) { | ||
| return `${createSkillFrontmatter(metadata)}\n\n${createManagedSkillBody(providerTitle, design)}`; | ||
| } |
+0
-1
@@ -157,3 +157,2 @@ "use strict"; | ||
| "spacingScale", | ||
| "componentFamilies", | ||
| "accessibilityRequirements", | ||
@@ -160,0 +159,0 @@ "writingTone", |
+10
-4
| { | ||
| "name": "typeui.sh", | ||
| "version": "0.5.0", | ||
| "version": "0.6.0", | ||
| "description": "Generate design system specifications and style guides as skill files for AI coding providers", | ||
@@ -24,9 +24,15 @@ "main": "dist/cli.js", | ||
| "type": "git", | ||
| "url": "https://github.com/bergside/typeui.sh" | ||
| "url": "https://github.com/bergside/typeui" | ||
| }, | ||
| "keywords": [ | ||
| "cli", | ||
| "design-system", | ||
| "design system", | ||
| "skills", | ||
| "ai" | ||
| "ai", | ||
| "design md", | ||
| "design skills", | ||
| "claude skills", | ||
| "ai skills", | ||
| "ui", | ||
| "ux" | ||
| ], | ||
@@ -33,0 +39,0 @@ "author": "https://bergside.com", |
+18
-16
@@ -24,3 +24,3 @@ ``` | ||
| Check out all [design systems](https://typeui.sh/design-systems) that can be pulled into your project. | ||
| Check out all [design systems](https://typeui.sh/design-skills) that can be pulled into your project. | ||
@@ -35,21 +35,23 @@ ## Available commands | ||
| | `list` | Show available registry specs from `bergside/awesome-design-skills` (with typeui.sh preview links), then pull one automatically. | | ||
| | `verify` | Verify your license key (pro version) and cache local license status. | | ||
| | `license` | Show local cached license status. | | ||
| | `clear-cache` | Remove local cache state (`~/.typeui-sh`). | | ||
| Shared options for `generate` and `update`: | ||
| ## Design Skill File Structure | ||
| - `-p, --providers <providers>` (comma-separated provider keys) | ||
| - `--dry-run` (preview changes without writing files) | ||
| Here's a breakdown of the design skill file that is being generated by the TypeUI CLI. | ||
| Shared options for `pull`: | ||
| | Section | What it does | | ||
| | --- | --- | | ||
| | `Mission` | Defines the design-system objective and expected output quality for the agent. | | ||
| | `Brand` | Captures product context and brand direction to anchor decisions. | | ||
| | `Style Foundations` | Defines core visual tokens and constraints (visual style, typography, color palette, spacing). | | ||
| | `Accessibility` | States accessibility standards and non-negotiable requirements. | | ||
| | `Writing Tone` | Sets tone/style for generated guidance language. | | ||
| | `Rules: Do` | Lists required implementation practices to follow. | | ||
| | `Rules: Don't` | Lists anti-patterns and prohibited behaviors. | | ||
| | `Expected Behavior` | Sets expectations for decision-making and trade-off handling. | | ||
| | `Guideline Authoring Workflow` | Gives the ordered process the agent should follow when producing guidelines. | | ||
| | `Required Output Structure` | Enforces the final response format for consistency and completeness. | | ||
| | `Component Rule Expectations` | Defines required interaction/state details in component guidance. | | ||
| | `Quality Gates` | Adds validation criteria for clarity, testability, and consistency. | | ||
| | `Example Constraint Language` | Standardizes wording strength (`must` vs `should`) and constraint style. | | ||
| - `-p, --providers <providers>` (comma-separated provider keys) | ||
| - `--dry-run` (preview changes without writing files) | ||
| Shared options for `list`: | ||
| - `-p, --providers <providers>` (comma-separated providers passed through to auto-pull) | ||
| - `--dry-run` (preview pull file changes without writing) | ||
| For local development: | ||
@@ -56,0 +58,0 @@ |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.readLicenseCache = readLicenseCache; | ||
| exports.writeLicenseCache = writeLicenseCache; | ||
| exports.isCacheRecordValid = isCacheRecordValid; | ||
| exports.fingerprintToken = fingerprintToken; | ||
| exports.clearLocalLicenseState = clearLocalLicenseState; | ||
| const node_crypto_1 = __importDefault(require("node:crypto")); | ||
| const promises_1 = __importDefault(require("node:fs/promises")); | ||
| const config_1 = require("../config"); | ||
| async function readLicenseCache() { | ||
| try { | ||
| const raw = await promises_1.default.readFile(config_1.LICENSE_CACHE_PATH, "utf8"); | ||
| const parsed = JSON.parse(raw); | ||
| if (!parsed.productId || !parsed.expiresAt || !parsed.licenseKeyFingerprint) { | ||
| return null; | ||
| } | ||
| if (parsed.productId !== config_1.PRODUCT_ID) { | ||
| return null; | ||
| } | ||
| return { | ||
| productId: parsed.productId, | ||
| verifiedAt: parsed.verifiedAt ?? new Date().toISOString(), | ||
| expiresAt: parsed.expiresAt, | ||
| licenseKeyFingerprint: parsed.licenseKeyFingerprint, | ||
| licenseKey: typeof parsed.licenseKey === "string" ? parsed.licenseKey : undefined | ||
| }; | ||
| } | ||
| catch (error) { | ||
| const e = error; | ||
| if (e.code === "ENOENT") { | ||
| return null; | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
| async function writeLicenseCache(record) { | ||
| await promises_1.default.mkdir(config_1.LICENSE_CACHE_DIR, { recursive: true }); | ||
| await promises_1.default.writeFile(config_1.LICENSE_CACHE_PATH, JSON.stringify(record, null, 2), "utf8"); | ||
| } | ||
| function isCacheRecordValid(record) { | ||
| const expires = new Date(record.expiresAt).getTime(); | ||
| return Number.isFinite(expires) && expires > Date.now(); | ||
| } | ||
| function fingerprintToken(token) { | ||
| return node_crypto_1.default.createHash("sha256").update(token).digest("hex").slice(0, 16); | ||
| } | ||
| async function clearLocalLicenseState() { | ||
| await promises_1.default.rm(config_1.LICENSE_CACHE_DIR, { recursive: true, force: true }); | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ensureVerifiedAccess = ensureVerifiedAccess; | ||
| exports.getVerifiedLicenseKey = getVerifiedLicenseKey; | ||
| exports.verifyAndCacheLicenseFromPrompt = verifyAndCacheLicenseFromPrompt; | ||
| exports.getCachedLicenseSummary = getCachedLicenseSummary; | ||
| exports.clearCachedLicenseState = clearCachedLicenseState; | ||
| const config_1 = require("../config"); | ||
| const license_1 = require("../prompts/license"); | ||
| const licenseCache_1 = require("./licenseCache"); | ||
| const polarClient_1 = require("./polarClient"); | ||
| function buildCacheRecord(licenseKey, expiresAt) { | ||
| return { | ||
| productId: config_1.PRODUCT_ID, | ||
| verifiedAt: new Date().toISOString(), | ||
| expiresAt, | ||
| licenseKeyFingerprint: (0, licenseCache_1.fingerprintToken)(licenseKey), | ||
| licenseKey | ||
| }; | ||
| } | ||
| function isInvalidLicenseReason(reason) { | ||
| const normalized = reason.toLowerCase(); | ||
| return (normalized === "not_found" || | ||
| normalized.includes("license_invalid") || | ||
| normalized.includes("license key is not valid") || | ||
| normalized.includes("invalid license")); | ||
| } | ||
| function withLicensePurchaseHelp(reason) { | ||
| if (!isInvalidLicenseReason(reason)) { | ||
| return reason; | ||
| } | ||
| return `${reason} You can get a license key at ${config_1.PRICING_URL}.`; | ||
| } | ||
| async function ensureVerifiedAccess() { | ||
| const cached = await (0, licenseCache_1.readLicenseCache)(); | ||
| if (cached && (0, licenseCache_1.isCacheRecordValid)(cached)) { | ||
| return; | ||
| } | ||
| const { licenseKey } = await (0, license_1.promptLicenseCredentials)(); | ||
| const verifyResult = await (0, polarClient_1.verifyPurchaseWithPolar)(licenseKey); | ||
| if (!verifyResult.ok) { | ||
| throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`); | ||
| } | ||
| await (0, licenseCache_1.writeLicenseCache)(buildCacheRecord(licenseKey, verifyResult.expiresAt)); | ||
| } | ||
| async function getVerifiedLicenseKey() { | ||
| const cached = await (0, licenseCache_1.readLicenseCache)(); | ||
| if (cached && (0, licenseCache_1.isCacheRecordValid)(cached) && cached.licenseKey) { | ||
| return cached.licenseKey; | ||
| } | ||
| const { licenseKey } = await (0, license_1.promptLicenseCredentials)(); | ||
| const verifyResult = await (0, polarClient_1.verifyPurchaseWithPolar)(licenseKey); | ||
| if (!verifyResult.ok) { | ||
| throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`); | ||
| } | ||
| await (0, licenseCache_1.writeLicenseCache)(buildCacheRecord(licenseKey, verifyResult.expiresAt)); | ||
| return licenseKey; | ||
| } | ||
| async function verifyAndCacheLicenseFromPrompt() { | ||
| const { licenseKey } = await (0, license_1.promptLicenseCredentials)(); | ||
| const verifyResult = await (0, polarClient_1.verifyPurchaseWithPolar)(licenseKey); | ||
| if (!verifyResult.ok) { | ||
| throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`); | ||
| } | ||
| const cacheRecord = buildCacheRecord(licenseKey, verifyResult.expiresAt); | ||
| await (0, licenseCache_1.writeLicenseCache)(cacheRecord); | ||
| return cacheRecord; | ||
| } | ||
| async function getCachedLicenseSummary() { | ||
| const cached = await (0, licenseCache_1.readLicenseCache)(); | ||
| if (!cached) { | ||
| return "No cached license."; | ||
| } | ||
| const status = (0, licenseCache_1.isCacheRecordValid)(cached) ? "valid" : "expired"; | ||
| return `Cached license (${cached.licenseKeyFingerprint}) is ${status} until ${cached.expiresAt}.`; | ||
| } | ||
| async function clearCachedLicenseState() { | ||
| await (0, licenseCache_1.clearLocalLicenseState)(); | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.verifyPurchaseWithPolar = verifyPurchaseWithPolar; | ||
| const config_1 = require("../config"); | ||
| const VERIFY_CACHE_TTL_DAYS = 31; | ||
| async function verifyPurchaseWithPolar(licenseKey) { | ||
| const verifyUrl = (0, config_1.getPolarVerifyUrl)(); | ||
| let response; | ||
| try { | ||
| response = await fetch(verifyUrl, { | ||
| method: "POST", | ||
| headers: { | ||
| "content-type": "application/json" | ||
| }, | ||
| body: JSON.stringify({ | ||
| licenseKey | ||
| }) | ||
| }); | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return { | ||
| ok: false, | ||
| reason: `Could not reach license server at ${verifyUrl}: ${message}. Please ensure your network connection is available and try again.` | ||
| }; | ||
| } | ||
| if (!response.ok) { | ||
| let serverReason; | ||
| try { | ||
| const errorData = (await response.json()); | ||
| serverReason = errorData.reason || errorData.error; | ||
| } | ||
| catch { | ||
| // Ignore JSON parse failures and fall back to status-only message. | ||
| } | ||
| return { | ||
| ok: false, | ||
| reason: serverReason | ||
| ? `License verification failed (${response.status}): ${serverReason}.` | ||
| : `License verification failed (${response.status}).` | ||
| }; | ||
| } | ||
| const data = (await response.json()); | ||
| if (!data.valid || data.reason !== "active" || data.status !== "granted") { | ||
| return { ok: false, reason: data.reason || data.error || "License key is not valid." }; | ||
| } | ||
| return { | ||
| ok: true, | ||
| // Local cache is fixed to 31 days from verification. | ||
| expiresAt: new Date(Date.now() + VERIFY_CACHE_TTL_DAYS * 24 * 60 * 60 * 1000).toISOString() | ||
| }; | ||
| } |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.promptLicenseCredentials = promptLicenseCredentials; | ||
| async function loadInquirer() { | ||
| const dynamicImport = new Function("specifier", "return import(specifier)"); | ||
| const inquirerModule = await dynamicImport("inquirer"); | ||
| return inquirerModule.default; | ||
| } | ||
| async function prompt(questions) { | ||
| const inquirer = await loadInquirer(); | ||
| return (await inquirer.prompt(questions)); | ||
| } | ||
| async function promptLicenseCredentials() { | ||
| const answers = await prompt([ | ||
| { | ||
| type: "password", | ||
| name: "licenseKey", | ||
| message: "License key:", | ||
| mask: "*", | ||
| validate: (value) => value.trim().length > 5 || "License key is required." | ||
| } | ||
| ]); | ||
| return { | ||
| licenseKey: answers.licenseKey.trim() | ||
| }; | ||
| } |
| # Registry Source (GitHub) | ||
| This document captures how CLI registry commands resolve data from the GitHub repository: | ||
| - [https://github.com/bergside/awesome-design-skills](https://github.com/bergside/awesome-design-skills) | ||
| ## Index source used by `list` and `pull` | ||
| Both commands read: | ||
| - `GET https://raw.githubusercontent.com/bergside/awesome-design-skills/main/skills/index.json` | ||
| Expected shape: | ||
| ```json | ||
| { | ||
| "paper": { | ||
| "slug": "paper", | ||
| "name": "Paper", | ||
| "skillPath": "skills/paper/SKILL.md" | ||
| } | ||
| } | ||
| ``` | ||
| The CLI maps each index entry to: | ||
| - `name` -> display name | ||
| - `slug` -> selection and pull key | ||
| - `previewUrl` -> repository page (`/tree/main/skills/<slug>`) | ||
| - `hasSkillMd` -> `true` when `skillPath` is non-empty | ||
| ## Pull behavior | ||
| For `pull <slug>`: | ||
| 1. Validate slug format locally. | ||
| 2. Read `skills/index.json`. | ||
| 3. Resolve `index[slug].skillPath`. | ||
| 4. Fetch markdown from raw GitHub URL: | ||
| - `GET https://raw.githubusercontent.com/bergside/awesome-design-skills/main/<skillPath>` | ||
| On success, response is markdown text (`text/markdown` or plain text accepted). | ||
| Common failure reasons surfaced by CLI: | ||
| - `not_found` (missing slug in index or missing markdown file) | ||
| - invalid index JSON/shape | ||
| - network/unreachable raw GitHub URLs |
-216
| # typeui.sh User Guide | ||
| This guide is for end users who run the `typeui.sh` CLI in a project to generate and maintain design-system skill files. | ||
| ## What typeui.sh does | ||
| `typeui.sh` asks you a series of interactive questions about your design system, then generates `SKILL.md` files for coding agents. | ||
| - Universal target is always generated: `.agents/skills/design-system/SKILL.md` | ||
| - You can optionally generate additional agent-specific files during the provider selection step. | ||
| ## Before you start | ||
| - Node.js 18+ recommended | ||
| - A valid license key from your Polar purchase (optional, only needed for pro features such as `verify`/`license`) | ||
| ## Install and run | ||
| Use the published CLI (recommended for end users): | ||
| ```bash | ||
| npx typeui.sh --help | ||
| ``` | ||
| Or if the package is already installed globally: | ||
| ```bash | ||
| typeui.sh --help | ||
| ``` | ||
| ## License key activation (optional, Polar purchase) | ||
| ### 1) Get your license key | ||
| After purchase, Polar provides your license key. Copy it exactly. | ||
| ### 2) Verify against the default endpoint | ||
| `typeui.sh` verifies your key directly with: | ||
| - `https://typeui.sh/api/license/verify` | ||
| ### 3) Activate in CLI (for license-aware commands) | ||
| Run: | ||
| ```bash | ||
| npx typeui.sh verify | ||
| ``` | ||
| You will be prompted for: | ||
| - `License key:` (hidden input) | ||
| If valid, the CLI caches your license locally in: | ||
| - `~/.typeui-sh/license.json` | ||
| Useful license commands: | ||
| - `npx typeui.sh license` - show cached license status | ||
| - `npx typeui.sh clear-cache` - clear local cache and force re-verification | ||
| ## Generate design-system skills (free) | ||
| Run: | ||
| ```bash | ||
| npx typeui.sh generate | ||
| ``` | ||
| ### Provider selection behavior | ||
| At the start of the flow: | ||
| - `.agents/skills` is included automatically | ||
| - A short list is shown so you know which agents are covered by the universal target | ||
| - You can select additional optional provider targets | ||
| ### Design-system interview flow | ||
| You will be prompted for: | ||
| 1. Product basics | ||
| - Product name | ||
| - Brand summary | ||
| 2. Visual style directions | ||
| - Multi-select preset list (+ optional custom values) | ||
| 3. Typography | ||
| - Typography scale strategy (exactly one selected) | ||
| - Primary UI font family (Google Fonts, choose one) | ||
| - Display/heading font family (Google Fonts, choose one) | ||
| - Monospace font family (Google Fonts, choose one) | ||
| - Core font weights (checkbox list, all selected by default) | ||
| 4. Color palette | ||
| - Palette guidance presets (+ optional custom values) | ||
| - Token values for primary, secondary, success, warning, danger, surface, text | ||
| 5. Spacing | ||
| - Spacing scale guidance (exactly one selected) | ||
| 6. Component families | ||
| - Multi-select list (+ optional custom values) | ||
| 7. Accessibility requirements | ||
| - Multi-select list (+ optional custom values) | ||
| 8. Writing tone | ||
| - Multi-select list (+ optional custom values) | ||
| 9. Rules | ||
| - Design DO rules (multi-select + optional custom) | ||
| - Design DON'T rules (multi-select + optional custom) | ||
| ## Update existing skill files | ||
| If files already exist, you can update selected design-system fields (free): | ||
| ```bash | ||
| npx typeui.sh update | ||
| ``` | ||
| The CLI will: | ||
| - Read existing managed content | ||
| - Ask which fields to update | ||
| - Rewrite only the managed block in each `SKILL.md` | ||
| ## Pull a published registry skill | ||
| Use this when you already know the published skill slug and want to fetch the authored markdown directly from [bergside/awesome-design-skills](https://github.com/bergside/awesome-design-skills): | ||
| ```bash | ||
| npx typeui.sh pull paper | ||
| ``` | ||
| Behavior: | ||
| - The CLI asks for provider targets the same way as `generate/update` (unless `--providers` is passed). | ||
| - Universal target is always included. | ||
| - The CLI fetches `skills/index.json` from the registry repository, resolves the slug's `skillPath`, then fetches the corresponding `SKILL.md`. | ||
| - On success, the pulled markdown is written to selected provider `SKILL.md` paths. | ||
| You can combine `pull` with `--providers` and `--dry-run`: | ||
| ```bash | ||
| npx typeui.sh pull paper --providers cursor,codex | ||
| npx typeui.sh pull paper --dry-run | ||
| ``` | ||
| ## List available registry specs | ||
| Use this to choose one available design-system spec from the GitHub registry, then pull it immediately: | ||
| ```bash | ||
| npx typeui.sh list | ||
| ``` | ||
| Behavior: | ||
| - The CLI fetches `skills/index.json` from `bergside/awesome-design-skills`. | ||
| - On success, it shows a checkbox selection where exactly one spec can be selected. | ||
| - After you confirm the selection, the CLI automatically runs pull for that slug. | ||
| You can pass pull options through list: | ||
| ```bash | ||
| npx typeui.sh list --providers cursor,codex | ||
| npx typeui.sh list --dry-run | ||
| ``` | ||
| ## Preview changes without writing files | ||
| Use `--dry-run` with generate, update, or pull: | ||
| ```bash | ||
| npx typeui.sh generate --dry-run | ||
| npx typeui.sh update --dry-run | ||
| npx typeui.sh pull paper --dry-run | ||
| ``` | ||
| ## Select providers from CLI options | ||
| You can bypass interactive provider selection: | ||
| ```bash | ||
| npx typeui.sh generate --providers cursor,claude-code,mistral-vibe | ||
| ``` | ||
| Notes: | ||
| - Provider keys are comma-separated. | ||
| - Universal target (`.agents/skills`) is still included automatically. | ||
| ## Where files are written | ||
| Every generated target writes: | ||
| - `.../skills/design-system/SKILL.md` | ||
| Examples: | ||
| - `.agents/skills/design-system/SKILL.md` (always) | ||
| - `.cursor/skills/design-system/SKILL.md` | ||
| - `.claude/skills/design-system/SKILL.md` | ||
| - `.codex/skills/design-system/SKILL.md` | ||
| - `.opencode/skills/design-system/SKILL.md` | ||
| ## Troubleshooting | ||
| - **"License verification failed"** | ||
| - Check your key. | ||
| - Confirm you can reach `https://www.typeui.sh/api/license/verify`. | ||
| - **"No cached license"** | ||
| - Run `npx typeui.sh verify`. | ||
| - **"No existing managed design system found" on update** | ||
| - Run `npx typeui.sh generate` first. | ||
| - **Wrong files generated** | ||
| - Re-run with `--dry-run` to verify target paths before writing. | ||
| - **"Registry pull failed: not_found"** | ||
| - Verify the slug exists and has published markdown. |
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
74
2.78%4
-33.33%70685
-14.68%22
-18.52%1712
-8.6%