chore(web): heroui skills

This commit is contained in:
2026-03-10 11:08:36 +08:00
parent 4d43ebd21e
commit 3ab225d76b
10 changed files with 1221 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
#!/usr/bin/env node
/**
* Get complete component documentation (MDX) for HeroUI v3 components.
*
* Usage:
* node get_component_docs.mjs Button
* node get_component_docs.mjs Button Card TextField
*
* Output:
* MDX documentation including imports, usage, variants, props, examples
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const FALLBACK_BASE = "https://v3.heroui.com";
const APP_PARAM = "app=react-skills";
/**
* Convert PascalCase to kebab-case.
*/
function toKebabCase(name) {
return name
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
.toLowerCase();
}
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
return null;
}
return await response.json();
} catch {
return null;
}
}
/**
* Fetch MDX directly from v3.heroui.com as fallback.
*/
async function fetchFallback(component) {
const kebabName = toKebabCase(component);
const url = `${FALLBACK_BASE}/docs/react/components/${kebabName}.mdx`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return {component, error: `Failed to fetch docs for ${component}`};
}
const content = await response.text();
return {
component,
content,
contentType: "mdx",
source: "fallback",
url,
};
} catch {
return {component, error: `Failed to fetch docs for ${component}`};
}
}
/**
* Main function to get component documentation.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_component_docs.mjs <Component1> [Component2] ...");
console.error("Example: node get_component_docs.mjs Button Card");
process.exit(1);
}
const components = args;
// Try API first - use POST /v1/components/docs for batch requests
console.error(`# Fetching docs for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/docs", "POST", {components});
if (data && data.results) {
// Output results
if (data.results.length === 1) {
// Single component - output content directly for easier reading
const result = data.results[0];
if (result.content) {
console.log(result.content);
} else if (result.error) {
console.error(`# Error for ${result.component}: ${result.error}`);
console.log(JSON.stringify(result, null, 2));
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
// Multiple components - output as JSON array
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to individual component fetches
console.error("# API failed, using fallback...");
const results = [];
for (const component of components) {
const result = await fetchFallback(component);
results.push(result);
}
// Output results
if (results.length === 1) {
// Single component - output content directly for easier reading
const result = results[0];
if (result.content) {
console.log(result.content);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
// Multiple components - output as JSON array
console.log(JSON.stringify(results, null, 2));
}
}
main();

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env node
/**
* Get non-component HeroUI documentation (guides, theming, releases).
*
* Usage:
* node get_docs.mjs /docs/react/getting-started/theming
* node get_docs.mjs /docs/react/releases/v3-0-0-beta-3
*
* Output:
* MDX documentation content
*
* Note: For component docs, use get_component_docs.mjs instead.
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const FALLBACK_BASE = "https://v3.heroui.com";
const APP_PARAM = "app=react-skills";
/**
* Fetch documentation from HeroUI API.
* Uses v1 endpoint pattern: /v1/docs/:path
*/
async function fetchApi(path) {
// The v1 API expects path without /docs/ prefix
// Input: /docs/react/getting-started/theming
// API expects: react/getting-started/theming (route is /v1/docs/:path(*))
let apiPath = path.startsWith("/docs/")
? path.slice(6) // Remove /docs/ prefix
: path.startsWith("/")
? path.slice(1) // Remove leading /
: path;
const separator = "?";
const url = `${API_BASE}/v1/docs/${apiPath}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch MDX directly from v3.heroui.com as fallback.
*/
async function fetchFallback(path) {
// Ensure path starts with /docs and ends with .mdx
let cleanPath = path.replace(/^\//, "");
if (!cleanPath.endsWith(".mdx")) {
cleanPath = `${cleanPath}.mdx`;
}
const url = `${FALLBACK_BASE}/${cleanPath}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return {error: `HTTP ${response.status}: ${response.statusText}`, path};
}
const content = await response.text();
return {
content,
contentType: "mdx",
path,
source: "fallback",
url,
};
} catch (error) {
return {error: `Fetch Error: ${error.message}`, path};
}
}
/**
* Main function to get documentation for specified path.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_docs.mjs <path>");
console.error("Example: node get_docs.mjs /docs/react/getting-started/theming");
console.error();
console.error("Available paths include:");
console.error(" /docs/react/getting-started/theming");
console.error(" /docs/react/getting-started/colors");
console.error(" /docs/react/getting-started/animations");
console.error(" /docs/react/releases/v3-0-0-beta-3");
console.error();
console.error("Note: For component docs, use get_component_docs.mjs instead.");
process.exit(1);
}
const path = args[0];
// Check if user is trying to get component docs
if (path.includes("/components/")) {
console.error("# Warning: Use get_component_docs.mjs for component documentation.");
const componentName = path.split("/").pop().replace(".mdx", "");
const titleCase = componentName.charAt(0).toUpperCase() + componentName.slice(1);
console.error(`# Example: node get_component_docs.mjs ${titleCase}`);
}
console.error(`# Fetching documentation for ${path}...`);
// Try API first
const data = await fetchApi(path);
if (data && data.content) {
data.source = "api";
console.log(data.content);
return;
}
// Fallback to direct fetch
console.error("# API failed, using fallback...");
const fallbackData = await fetchFallback(path);
if (fallbackData.content) {
console.log(fallbackData.content);
} else {
console.log(JSON.stringify(fallbackData, null, 2));
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* Get React/TypeScript source code implementation for HeroUI v3 components.
*
* Usage:
* node get_source.mjs Button
* node get_source.mjs Button Accordion Card
*
* Output:
* Full TSX source code with GitHub URL for each component
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
const APP_PARAM = "app=react-skills";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch source code directly from GitHub as fallback.
*/
async function fetchGithubFallback(component) {
// Try common patterns for component paths
const patterns = [
`packages/react/src/components/${component.toLowerCase()}/${component.toLowerCase()}.tsx`,
`packages/react/src/components/${component.toLowerCase()}/index.tsx`,
];
for (const path of patterns) {
const url = `${GITHUB_RAW_BASE}/${path}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (response.ok) {
const content = await response.text();
return {
component,
filePath: path,
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
source: "fallback",
sourceCode: content,
};
}
} catch {
continue;
}
}
return {component, error: `Failed to fetch source for ${component}`};
}
/**
* Main function to get source code for specified components.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_source.mjs <Component1> [Component2] ...");
console.error("Example: node get_source.mjs Button Accordion");
process.exit(1);
}
const components = args;
// Try API first
console.error(`# Fetching source code for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/source", "POST", {components});
if (data && data.results) {
for (const result of data.results) {
result.source = "api";
}
// Output results
if (data.results.length === 1) {
const result = data.results[0];
if (result.sourceCode) {
console.log(`// File: ${result.filePath || "unknown"}`);
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
console.log();
console.log(result.sourceCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to GitHub direct fetch
console.error("# API failed, using GitHub fallback...");
const results = [];
for (const component of components) {
const result = await fetchGithubFallback(component);
results.push(result);
}
if (results.length === 1) {
const result = results[0];
if (result.sourceCode) {
console.log(`// File: ${result.filePath || "unknown"}`);
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
console.log();
console.log(result.sourceCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify({results}, null, 2));
}
}
main();

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* Get CSS styles (BEM classes) for HeroUI v3 components.
*
* Usage:
* node get_styles.mjs Button
* node get_styles.mjs Button Card Chip
*
* Output:
* CSS file content with BEM classes and GitHub URL for each component
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
const APP_PARAM = "app=react-skills";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch CSS styles directly from GitHub as fallback.
*/
async function fetchGithubFallback(component) {
// Try common patterns for style paths
const patterns = [
`packages/styles/src/components/${component.toLowerCase()}.css`,
`packages/styles/components/${component.toLowerCase()}.css`,
];
for (const path of patterns) {
const url = `${GITHUB_RAW_BASE}/${path}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (response.ok) {
const content = await response.text();
return {
component,
filePath: path,
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
source: "fallback",
stylesCode: content,
};
}
} catch {
continue;
}
}
return {component, error: `Failed to fetch styles for ${component}`};
}
/**
* Main function to get CSS styles for specified components.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_styles.mjs <Component1> [Component2] ...");
console.error("Example: node get_styles.mjs Button Card");
process.exit(1);
}
const components = args;
// Try API first
console.error(`# Fetching styles for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/styles", "POST", {components});
if (data && data.results) {
for (const result of data.results) {
result.source = "api";
}
// Output results
if (data.results.length === 1) {
const result = data.results[0];
if (result.stylesCode) {
console.log(`/* File: ${result.filePath || "unknown"} */`);
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
console.log();
console.log(result.stylesCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to GitHub direct fetch
console.error("# API failed, using GitHub fallback...");
const results = [];
for (const component of components) {
const result = await fetchGithubFallback(component);
results.push(result);
}
if (results.length === 1) {
const result = results[0];
if (result.stylesCode) {
console.log(`/* File: ${result.filePath || "unknown"} */`);
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
console.log();
console.log(result.stylesCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify({results}, null, 2));
}
}
main();

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env node
/**
* Get theme variables and design tokens for HeroUI v3.
*
* Usage:
* node get_theme.mjs
*
* Output:
* Theme variables organized by common/light/dark with oklch color format
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const APP_PARAM = "app=react-skills";
// Fallback theme reference when API is unavailable
const FALLBACK_THEME = {
common: {
base: [
{name: "--font-sans", value: "ui-sans-serif, system-ui, sans-serif"},
{name: "--font-mono", value: "ui-monospace, monospace"},
{name: "--radius-sm", value: "0.375rem"},
{name: "--radius-md", value: "0.5rem"},
{name: "--radius-lg", value: "0.75rem"},
{name: "--radius-full", value: "9999px"},
],
calculated: [{name: "--spacing-unit", value: "0.25rem"}],
},
dark: {
semantic: [
{name: "--color-background", value: "oklch(14.5% 0 0)"},
{name: "--color-foreground", value: "oklch(98.4% 0 0)"},
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
],
},
latestVersion: "3.0.0-beta",
light: {
semantic: [
{name: "--color-background", value: "oklch(100% 0 0)"},
{name: "--color-foreground", value: "oklch(14.5% 0 0)"},
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
],
},
note: "This is a fallback. For complete theme variables, ensure the API is accessible.",
source: "fallback",
theme: "default",
};
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Format theme variables for display.
*/
function formatVariables(variables) {
const lines = [];
for (const variable of variables) {
const name = variable.name || "";
const value = variable.value || "";
const desc = variable.description || "";
if (desc) {
lines.push(` ${name}: ${value}; /* ${desc} */`);
} else {
lines.push(` ${name}: ${value};`);
}
}
return lines.join("\n");
}
/**
* Main function to get theme variables.
*/
async function main() {
console.error("# Fetching theme variables...");
const rawData = await fetchApi("/v1/themes/variables?theme=default");
let data;
let version;
if (!rawData) {
console.error("# API failed, using fallback theme reference...");
data = FALLBACK_THEME;
version = FALLBACK_THEME.latestVersion || "unknown";
} else {
// Handle API response format: { themes: [...], latestVersion: "..." }
if (rawData.themes && rawData.themes.length > 0) {
data = rawData.themes[0]; // Get first theme (default)
version = rawData.latestVersion || rawData.version || "unknown";
} else {
// Direct format
data = rawData;
version = rawData.latestVersion || "unknown";
}
}
// Output as formatted CSS-like structure for readability
console.log("/* HeroUI v3 Theme Variables */");
console.log(`/* Theme: ${data.theme || "default"} */`);
console.log(`/* Version: ${version} */`);
console.log();
// Common variables
if (data.common) {
console.log(":root {");
console.log(" /* Base Variables */");
if (data.common.base) {
console.log(formatVariables(data.common.base));
}
console.log();
console.log(" /* Calculated Variables */");
if (data.common.calculated) {
console.log(formatVariables(data.common.calculated));
}
console.log("}");
console.log();
}
// Light mode
if (data.light) {
console.log(":root, [data-theme='light'] {");
console.log(" /* Light Mode Semantic Variables */");
if (data.light.semantic) {
console.log(formatVariables(data.light.semantic));
}
console.log("}");
console.log();
}
// Dark mode
if (data.dark) {
console.log("[data-theme='dark'] {");
console.log(" /* Dark Mode Semantic Variables */");
if (data.dark.semantic) {
console.log(formatVariables(data.dark.semantic));
}
console.log("}");
}
// Also output raw JSON to stderr for programmatic use
console.error("\n# Raw JSON output:");
console.error(JSON.stringify(rawData || data, null, 2));
}
main();

View File

@@ -0,0 +1,134 @@
#!/usr/bin/env node
/**
* List all available HeroUI v3 components.
*
* Usage:
* node list_components.mjs
*
* Output:
* JSON with components array, latestVersion, and count
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const APP_PARAM = "app=react-skills";
const LLMS_TXT_URL = "https://v3.heroui.com/react/llms.txt";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`HTTP Error ${response.status}: ${response.statusText}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`API Error: ${error.message}`);
return null;
}
}
/**
* Fetch component list from llms.txt fallback URL.
*/
async function fetchFallback() {
try {
const response = await fetch(LLMS_TXT_URL, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return null;
}
const content = await response.text();
// Parse markdown to extract component names from pattern: - [ComponentName](url)
// Look for links under the Components section (### Components)
const components = [];
let inComponentsSection = false;
for (const line of content.split("\n")) {
// Check if we're entering the Components section (uses ### header)
if (line.trim() === "### Components") {
inComponentsSection = true;
continue;
}
// Check if we're leaving the Components section (another ### header)
if (inComponentsSection && line.trim().startsWith("### ")) {
break;
}
// Extract component name from markdown link pattern
// Match: - [ComponentName](https://v3.heroui.com/docs/react/components/component-name)
// Skip "All Components" which links to /components without a specific component
if (inComponentsSection) {
const match = line.match(
/^\s*-\s*\[([^\]]+)\]\(https:\/\/v3\.heroui\.com\/docs\/react\/components\/[a-z]/,
);
if (match) {
components.push(match[1]);
}
}
}
if (components.length > 0) {
console.error(`# Using fallback: ${LLMS_TXT_URL}`);
return {
components: components.sort(),
count: components.length,
latestVersion: "unknown",
};
}
return null;
} catch (error) {
console.error(`Fallback Error: ${error.message}`);
return null;
}
}
/**
* Main function to list all available HeroUI v3 components.
*/
async function main() {
let data = await fetchApi("/v1/components");
// Check if API returned valid data with components
if (!data || !data.components || data.components.length === 0) {
console.error("# API returned no components, trying fallback...");
data = await fetchFallback();
}
if (!data || !data.components || data.components.length === 0) {
console.error("Error: Failed to fetch component list from API and fallback");
process.exit(1);
}
// Output formatted JSON
console.log(JSON.stringify(data, null, 2));
// Print summary to stderr for human readability
console.error(
`\n# Found ${data.components.length} components (${data.latestVersion || "unknown"})`,
);
}
main();