
Baoyu Markdown To Html
Turn markdown drafts into themed, image-aware HTML documents ready to publish or ship in static sites and newsletters.
Install
npx skills add https://github.com/ideacco/baoyu-skills-openclaw --skill baoyu-markdown-to-htmlWhat is this skill?
- Full markdown render pipeline with post-processing and HTML document builder
- Theme CSS loading with defaults and normalized inline styles
- Remote image download with local path substitution and content image tracking
- Timestamped backups and parsed metadata (title, author, summary)
- Configurable style objects aligned with HtmlDocumentMeta types
Adoption & trust: 1 installs on skills.sh; 12 GitHub stars; 2/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
Recommended Skills
Journey fit
HTML generation is the build-time step that turns notes and specs into shippable pages, even when you publish later in Launch or Grow. Docs and long-form content in repos are the usual input—this skill is the formatter between markdown source and a presentable HTML artifact.
Common Questions / FAQ
Is Baoyu Markdown To Html safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Baoyu Markdown To Html
import fs from 'node:fs'; import path from 'node:path'; import { createHash } from 'node:crypto'; import https from 'node:https'; import http from 'node:http'; import process from 'node:process'; import type { StyleConfig, HtmlDocumentMeta } from './md/types.js'; import { DEFAULT_STYLE, THEME_STYLE_DEFAULTS } from './md/constants.js'; import { loadThemeCss, normalizeThemeCss } from './md/themes.js'; import { initRenderer, renderMarkdown, postProcessHtml } from './md/renderer.js'; import { buildCss, loadCodeThemeCss, buildHtmlDocument, inlineCss, normalizeInlineCss, modifyHtmlStructure, removeFirstHeading, } from './md/html-builder.js'; interface ImageInfo { placeholder: string; localPath: string; originalPath: string; } interface ParsedResult { title: string; author: string; summary: string; htmlPath: string; backupPath?: string; contentImages: ImageInfo[]; } function formatTimestamp(date = new Date()): string { const pad = (v: number) => String(v).padStart(2, '0'); return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`; } function downloadFile(url: string, destPath: string): Promise<void> { return new Promise((resolve, reject) => { const protocol = url.startsWith('https') ? https : http; const file = fs.createWriteStream(destPath); const request = protocol.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (response) => { if (response.statusCode === 301 || response.statusCode === 302) { const redirectUrl = response.headers.location; if (redirectUrl) { file.close(); fs.unlinkSync(destPath); downloadFile(redirectUrl, destPath).then(resolve).catch(reject); return; } } if (response.statusCode !== 200) { file.close(); fs.unlinkSync(destPath); reject(new Error(`Failed to download: ${response.statusCode}`)); return; } response.pipe(file); file.on('finish', () => { file.close(); resolve(); }); }); request.on('error', (err) => { file.close(); fs.unlink(destPath, () => {}); reject(err); }); request.setTimeout(30000, () => { request.destroy(); reject(new Error('Download timeout')); }); }); } function getImageExtension(urlOrPath: string): string { const match = urlOrPath.match(/\.(jpg|jpeg|png|gif|webp)(\?|$)/i); return match ? match[1]!.toLowerCase() : 'png'; } async function resolveImagePath(imagePath: string, baseDir: string, tempDir: string): Promise<string> { if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) { const hash = createHash('md5').update(imagePath).digest('hex').slice(0, 8); const ext = getImageExtension(imagePath); const localPath = path.join(tempDir, `remote_${hash}.${ext}`); if (!fs.existsSync(localPath)) { console.error(`[markdown-to-html] Downloading: ${imagePath}`); await downloadFile(imagePath, localPath); } return localPath; } if (path.isAbsolute(imagePath)) { return imagePath; } return path.resolve(baseDir, imagePath); } function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } { const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); if (!match) return { frontmatter: {}, body: content }; const frontmatter: Record<string, string> = {}; const lines = match[1]!.split('\n'); for (const line of lines) { const colonIdx = line.indexOf(':'); if (colonIdx > 0) { const key = line.slice(0, colonIdx).trim(); let value = line.slice(colonIdx + 1).trim(); if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } frontmatter[key] = value; } } return { frontmatter, body: match[2]! }; } export async f