update: remove redundant commands and component families

This commit is contained in:
Zoltán Szőgyényi
2026-04-03 16:32:31 +03:00
parent b606715a89
commit 6277a40b56
18 changed files with 25 additions and 422 deletions
@@ -42,16 +42,6 @@ One paragraph describing the system objective and target product experience.
- Spacing scale: [token list]
- Radius/shadow/motion tokens: [if applicable]
## Component Families
- buttons
- inputs
- forms
- navigation
- overlays
- feedback
- data display
- etc.
## Accessibility
- Target: WCAG 2.2 AA
- Keyboard-first interactions required
@@ -107,7 +97,6 @@ concise, confident, implementation-focused
- `[brand-or-scope]` in `name`
- System name and mission
- Token values (typography, colors, spacing, motion)
- Component family list based on product scope
- Any framework-specific implementation notes
## Optional Extensions
+25 -3
View File
@@ -31,9 +31,6 @@ Check out all [design systems](https://typeui.sh/design-skills) that can be pull
| `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. |
| `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`:
@@ -50,6 +47,31 @@ Shared options for `list`:
- `-p, --providers <providers>` (comma-separated providers passed through to auto-pull)
- `--dry-run` (preview pull file changes without writing)
## Design Skill File Structure
Generated files include YAML frontmatter plus a managed content block between:
- `<!-- TYPEUI_SH_MANAGED_START -->`
- `<!-- TYPEUI_SH_MANAGED_END -->`
Within the managed block, sections are structured like this:
| 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. |
For local development:
```bash
-6
View File
@@ -37,12 +37,6 @@ Use `typeui.sh` to generate, update, list, and pull design-system skill files fo
- Pull a specific registry skill and write it to selected provider paths.
- `npx typeui.sh list`
- List available registry slugs, show preview links, and pull one selection.
- `npx typeui.sh verify`
- Verify and cache license state for license-aware workflows.
- `npx typeui.sh license`
- Show local cached license summary.
- `npx typeui.sh clear-cache`
- Clear local `~/.typeui-sh` cache state.
## Local Dev Invocation (This Repo)
-28
View File
@@ -1,11 +1,6 @@
#!/usr/bin/env node
import path from "node:path";
import { Command } from "commander";
import {
clearCachedLicenseState,
getCachedLicenseSummary,
verifyAndCacheLicenseFromPrompt
} from "./licensing/licenseService";
import {
promptDesignSystem,
promptDesignSystemFields,
@@ -184,29 +179,6 @@ program
await listLike(options);
});
program
.command("verify")
.description("Verify your license key and cache local license status.")
.action(async () => {
const record = await verifyAndCacheLicenseFromPrompt();
console.log(`License cached (${record.licenseKeyFingerprint}) until ${record.expiresAt}`);
});
program
.command("license")
.description("Show local cached license status.")
.action(async () => {
console.log(await getCachedLicenseSummary());
});
program
.command("clear-cache")
.description("Clear all local typeui.sh cache state.")
.action(async () => {
await clearCachedLicenseState();
console.log("Cleared local cache state.");
});
program.parseAsync().catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
console.error(`typeui.sh error: ${message}`);
-13
View File
@@ -1,24 +1,11 @@
import path from "node:path";
import os from "node:os";
export const PRODUCT_ID = "typeui.sh";
export const MANAGED_BLOCK_START = "<!-- TYPEUI_SH_MANAGED_START -->";
export const MANAGED_BLOCK_END = "<!-- TYPEUI_SH_MANAGED_END -->";
export const API_DOMAIN = "https://www.typeui.sh";
export const GITHUB_REGISTRY_REPO_URL = "https://github.com/bergside/awesome-design-skills";
export const GITHUB_REGISTRY_RAW_BASE_URL =
"https://raw.githubusercontent.com/bergside/awesome-design-skills/main";
export const PRICING_URL = "https://www.typeui.sh/#pricing";
export const POLAR_VERIFY_URL = `${API_DOMAIN}/api/license/verify`;
export const REGISTRY_SPECS_URL = `${GITHUB_REGISTRY_RAW_BASE_URL}/skills/index.json`;
export const LICENSE_CACHE_DIR = path.join(os.homedir(), ".typeui-sh");
export const LICENSE_CACHE_PATH = path.join(LICENSE_CACHE_DIR, "license.json");
export function getPolarVerifyUrl(): string {
return POLAR_VERIFY_URL;
}
export function getRegistryPullUrl(skillPath: string): string {
const encodedPath = skillPath
.split("/")
-2
View File
@@ -28,7 +28,6 @@ export const DesignSystemSchema = z.object({
typographyScale: z.string().min(3),
colorPalette: z.string().min(3),
spacingScale: z.string().min(3),
componentFamilies: z.array(z.string().min(1)).min(1),
accessibilityRequirements: z.string().min(3),
writingTone: z.string().min(3),
doRules: z.array(z.string().min(1)).min(1),
@@ -60,7 +59,6 @@ export const FlatDesignSystemPromptSchema = z.object({
typographyScale: z.string().min(3),
colorPalette: z.string().min(3),
spacingScale: z.string().min(3),
componentFamilies: nonEmptyList,
accessibilityRequirements: z.string().min(3),
writingTone: z.string().min(3),
doRules: nonEmptyList,
-3
View File
@@ -78,7 +78,6 @@ export function parseManagedDesignSystem(content: string): DesignSystemInput | n
const typographyScale = extractStyleValue(managed, "Typography scale");
const colorPalette = extractStyleValue(managed, "Color palette");
const spacingScale = extractStyleValue(managed, "Spacing scale");
const componentFamilies = extractListSection(managed, "## Component Families", "## Accessibility");
const accessibilityRequirements = extractSection(managed, "## Accessibility", "## Writing Tone");
const writingTone = extractSection(managed, "## Writing Tone", "## Rules: Do");
const doRules = extractListSection(managed, "## Rules: Do", "## Rules: Don't");
@@ -91,7 +90,6 @@ export function parseManagedDesignSystem(content: string): DesignSystemInput | n
!typographyScale ||
!colorPalette ||
!spacingScale ||
!componentFamilies ||
!accessibilityRequirements ||
!writingTone ||
!doRules ||
@@ -107,7 +105,6 @@ export function parseManagedDesignSystem(content: string): DesignSystemInput | n
typographyScale,
colorPalette,
spacingScale,
componentFamilies,
accessibilityRequirements,
writingTone,
doRules,
-48
View File
@@ -1,48 +0,0 @@
import crypto from "node:crypto";
import fs from "node:fs/promises";
import { LICENSE_CACHE_DIR, LICENSE_CACHE_PATH, PRODUCT_ID } from "../config";
import { LicenseCacheRecord } from "../types";
export async function readLicenseCache(): Promise<LicenseCacheRecord | null> {
try {
const raw = await fs.readFile(LICENSE_CACHE_PATH, "utf8");
const parsed = JSON.parse(raw) as Partial<LicenseCacheRecord>;
if (!parsed.productId || !parsed.expiresAt || !parsed.licenseKeyFingerprint) {
return null;
}
if (parsed.productId !== 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 as NodeJS.ErrnoException;
if (e.code === "ENOENT") {
return null;
}
throw error;
}
}
export async function writeLicenseCache(record: LicenseCacheRecord): Promise<void> {
await fs.mkdir(LICENSE_CACHE_DIR, { recursive: true });
await fs.writeFile(LICENSE_CACHE_PATH, JSON.stringify(record, null, 2), "utf8");
}
export function isCacheRecordValid(record: LicenseCacheRecord): boolean {
const expires = new Date(record.expiresAt).getTime();
return Number.isFinite(expires) && expires > Date.now();
}
export function fingerprintToken(token: string): string {
return crypto.createHash("sha256").update(token).digest("hex").slice(0, 16);
}
export async function clearLocalLicenseState(): Promise<void> {
await fs.rm(LICENSE_CACHE_DIR, { recursive: true, force: true });
}
-97
View File
@@ -1,97 +0,0 @@
import { PRICING_URL, PRODUCT_ID } from "../config";
import { promptLicenseCredentials } from "../prompts/license";
import { LicenseCacheRecord } from "../types";
import {
clearLocalLicenseState,
fingerprintToken,
isCacheRecordValid,
readLicenseCache,
writeLicenseCache
} from "./licenseCache";
import { verifyPurchaseWithPolar } from "./polarClient";
function buildCacheRecord(licenseKey: string, expiresAt: string): LicenseCacheRecord {
return {
productId: PRODUCT_ID,
verifiedAt: new Date().toISOString(),
expiresAt,
licenseKeyFingerprint: fingerprintToken(licenseKey),
licenseKey
};
}
function isInvalidLicenseReason(reason: string): boolean {
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: string): string {
if (!isInvalidLicenseReason(reason)) {
return reason;
}
return `${reason} You can get a license key at ${PRICING_URL}.`;
}
export async function ensureVerifiedAccess(): Promise<void> {
const cached = await readLicenseCache();
if (cached && isCacheRecordValid(cached)) {
return;
}
const { licenseKey } = await promptLicenseCredentials();
const verifyResult = await verifyPurchaseWithPolar(licenseKey);
if (!verifyResult.ok) {
throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`);
}
await writeLicenseCache(buildCacheRecord(licenseKey, verifyResult.expiresAt));
}
export async function getVerifiedLicenseKey(): Promise<string> {
const cached = await readLicenseCache();
if (cached && isCacheRecordValid(cached) && cached.licenseKey) {
return cached.licenseKey;
}
const { licenseKey } = await promptLicenseCredentials();
const verifyResult = await verifyPurchaseWithPolar(licenseKey);
if (!verifyResult.ok) {
throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`);
}
await writeLicenseCache(buildCacheRecord(licenseKey, verifyResult.expiresAt));
return licenseKey;
}
export async function verifyAndCacheLicenseFromPrompt(): Promise<LicenseCacheRecord> {
const { licenseKey } = await promptLicenseCredentials();
const verifyResult = await verifyPurchaseWithPolar(licenseKey);
if (!verifyResult.ok) {
throw new Error(`License verification failed: ${withLicensePurchaseHelp(verifyResult.reason)}`);
}
const cacheRecord = buildCacheRecord(licenseKey, verifyResult.expiresAt);
await writeLicenseCache(cacheRecord);
return cacheRecord;
}
export async function getCachedLicenseSummary(): Promise<string> {
const cached = await readLicenseCache();
if (!cached) {
return "No cached license.";
}
const status = isCacheRecordValid(cached) ? "valid" : "expired";
return `Cached license (${cached.licenseKeyFingerprint}) is ${status} until ${cached.expiresAt}.`;
}
export async function clearCachedLicenseState(): Promise<void> {
await clearLocalLicenseState();
}
-73
View File
@@ -1,73 +0,0 @@
import { getPolarVerifyUrl } from "../config";
export interface PolarVerifySuccess {
ok: true;
expiresAt: string;
}
export interface PolarVerifyFailure {
ok: false;
reason: string;
}
export type PolarVerifyResult = PolarVerifySuccess | PolarVerifyFailure;
interface PolarResponseShape {
valid?: boolean;
reason?: string;
status?: string;
expires_at?: string;
expiresAt?: string;
error?: string;
}
const VERIFY_CACHE_TTL_DAYS = 31;
export async function verifyPurchaseWithPolar(licenseKey: string): Promise<PolarVerifyResult> {
const verifyUrl = getPolarVerifyUrl();
let response: 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: string | undefined;
try {
const errorData = (await response.json()) as PolarResponseShape;
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()) as PolarResponseShape;
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()
};
}
-63
View File
@@ -52,7 +52,6 @@ const designFieldChoices: { name: string; value: DesignSystemField }[] = [
{ name: "Typography scale", value: "typographyScale" },
{ name: "Color palette", value: "colorPalette" },
{ name: "Spacing scale", value: "spacingScale" },
{ name: "Component families", value: "componentFamilies" },
{ name: "Accessibility requirements", value: "accessibilityRequirements" },
{ name: "Writing tone", value: "writingTone" },
{ name: "DO rules", value: "doRules" },
@@ -344,52 +343,6 @@ const SPACING_SCALE_OPTIONS = [
"comfortable density mode"
];
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 = [
"WCAG 2.2 AA",
"keyboard-first interactions",
@@ -597,11 +550,6 @@ export async function promptDesignSystem(defaultProductName = "typeui.sh"): Prom
presets: SPACING_SCALE_OPTIONS,
defaultChoice: "4/8/12/16/24/32"
});
const componentFamilies = await promptPresetSelection({
message: "Select component families to prioritize:",
presets: COMPONENT_FAMILY_OPTIONS,
defaultSelected: COMPONENT_FAMILY_OPTIONS
});
const accessibilityRequirements = await promptPresetSelection({
message: "Select accessibility requirements:",
presets: ACCESSIBILITY_OPTIONS,
@@ -638,7 +586,6 @@ export async function promptDesignSystem(defaultProductName = "typeui.sh"): Prom
typographyScale,
colorPalette,
spacingScale,
componentFamilies,
accessibilityRequirements: accessibilityRequirements.join(", "),
writingTone: writingTone.join(", "),
doRules,
@@ -720,16 +667,6 @@ export async function promptDesignSystemUpdates(
updates.spacingScale = value;
break;
}
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": {
const defaults = matchPresetDefaults(current.accessibilityRequirements, ACCESSIBILITY_OPTIONS);
const values = await promptPresetSelection({
-35
View File
@@ -1,35 +0,0 @@
type InquirerModule = typeof import("inquirer");
async function loadInquirer(): Promise<InquirerModule["default"]> {
const dynamicImport = new Function(
"specifier",
"return import(specifier)"
) as (specifier: string) => Promise<InquirerModule>;
const inquirerModule = await dynamicImport("inquirer");
return inquirerModule.default;
}
async function prompt<T>(questions: unknown): Promise<T> {
const inquirer = await loadInquirer();
return (await inquirer.prompt(questions as never)) as T;
}
export interface LicensePromptAnswers {
licenseKey: string;
}
export async function promptLicenseCredentials(): Promise<LicensePromptAnswers> {
const answers = await prompt<LicensePromptAnswers>([
{
type: "password",
name: "licenseKey",
message: "License key:",
mask: "*",
validate: (value: string) => value.trim().length > 5 || "License key is required."
}
]);
return {
licenseKey: answers.licenseKey.trim()
};
}
-3
View File
@@ -24,9 +24,6 @@ export function createManagedSkillBody(providerTitle: string, design: DesignSyst
`- Color palette: ${design.colorPalette}`,
`- Spacing scale: ${design.spacingScale}`,
"",
"## Component Families",
list(design.componentFamilies),
"",
"## Accessibility",
design.accessibilityRequirements,
"",
-10
View File
@@ -158,7 +158,6 @@ export interface DesignSystemInput {
typographyScale: string;
colorPalette: string;
spacingScale: string;
componentFamilies: string[];
accessibilityRequirements: string;
writingTone: string;
doRules: string[];
@@ -177,7 +176,6 @@ export const DESIGN_SYSTEM_FIELDS = [
"typographyScale",
"colorPalette",
"spacingScale",
"componentFamilies",
"accessibilityRequirements",
"writingTone",
"doRules",
@@ -191,11 +189,3 @@ export interface ProviderFile {
relativePath: string;
content: string;
}
export interface LicenseCacheRecord {
productId: string;
verifiedAt: string;
expiresAt: string;
licenseKeyFingerprint: string;
licenseKey?: string;
}
-1
View File
@@ -10,7 +10,6 @@ const sampleDesign: DesignSystemInput = {
typographyScale: "12/14/16/20/24/32",
colorPalette: "primary, neutral, semantic",
spacingScale: "4/8/12/16/24/32",
componentFamilies: ["buttons", "inputs", "cards"],
accessibilityRequirements: "WCAG 2.2 AA",
writingTone: "clear and direct",
doRules: ["use semantic tokens", "preserve hierarchy"],
-25
View File
@@ -1,25 +0,0 @@
import { describe, expect, it } from "vitest";
import { isCacheRecordValid } from "../src/licensing/licenseCache";
describe("isCacheRecordValid", () => {
it("returns true for future expiry", () => {
const valid = isCacheRecordValid({
productId: "typeui.sh",
verifiedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 60_000).toISOString(),
licenseKeyFingerprint: "abcd",
licenseKey: "license_valid_123"
});
expect(valid).toBe(true);
});
it("returns false for expired cache", () => {
const valid = isCacheRecordValid({
productId: "typeui.sh",
verifiedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() - 60_000).toISOString(),
licenseKeyFingerprint: "abcd"
});
expect(valid).toBe(false);
});
});
-1
View File
@@ -9,7 +9,6 @@ const sampleDesign: DesignSystemInput = {
typographyScale: "12/14/16/20/24/32",
colorPalette: "primary, neutral, semantic",
spacingScale: "4/8/12/16/24/32",
componentFamilies: ["buttons", "inputs", "cards"],
accessibilityRequirements: "WCAG 2.2 AA",
writingTone: "clear and direct",
doRules: ["use semantic tokens", "preserve hierarchy"],