.claude/skills/export-to-html/SKILL.md
Export a markdown file to a self-contained HTML folder using Jinja templates. Creates an output folder with HTML, CSS, and image assets from the template. Supports metadata like title, author, client, date via YAML frontmatter or arguments. Triggers: "export to html", "render markdown", "convert to html", "make html", "export document", "render document", "html export".
npx skillsauth add johncarpenter/knowledge-base export-to-htmlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Export markdown files to self-contained HTML folders with all assets included.
/export-to-html <markdown-file> [template] [options]
| Argument | Required | Default | Description |
|----------|----------|---------|-------------|
| markdown-file | Yes | - | Path to the source markdown file |
| template | No | 2lines-external | Template directory name |
| Option | Description |
|--------|-------------|
| --title | Document title (overrides frontmatter) |
| --subtitle | Document subtitle or one-line description |
| --author | Author name (default: "John Carpenter, 2Lines Software") |
| --client | Client name (for external docs) |
| --date | Document date (default: today, e.g., "February 2026") |
| --version | Document version (default: "1.0") |
| --classification | Classification level (default: "Confidential") |
| --output | Output folder path (default: same location as source, named after file) |
/export-to-html clients/Circuit/proposal.md
/export-to-html research/technical-spike.md --title "Technical Analysis" --client "Acme Corp"
/export-to-html pipeline/rfp-response.md 2lines-external --output exports/rfp
The skill creates a self-contained folder with all necessary assets:
<document-name>/
├── index.html # Rendered HTML document
├── style.css # Stylesheet from template
└── logo.svg # Logo and other images from template
Example: For clients/Circuit/proposal.md, output is:
clients/Circuit/proposal/
├── index.html
├── style.css
└── logo.svg
| Template | Directory | Purpose |
|----------|-----------|---------|
| 2lines-external | _templates/2lines-external/ | External documents (proposals, reports, deliverables) |
Each template directory contains:
_templates/<template-name>/
├── base.html # Main template with cover page, TOC, and body structure
├── style.css # Stylesheet (copied to output folder)
└── logo.svg # 2Lines logo (copied to output folder)
The 2lines-external template includes:
Extract the markdown file path, template name, and any metadata options from the user's request.
# Example parsing
markdown_file = "clients/Circuit/proposal.md"
template_name = "2lines-external" # default
options = {
"title": "Project Proposal",
"client": "Circuit",
"classification": "Confidential"
}
from pathlib import Path
# Read the source markdown
md_path = Path(markdown_file)
if not md_path.is_absolute():
md_path = Path("/Users/john/Documents/Workspace/2Lines/knowledge-base") / md_path
md_content = md_path.read_text()
If the markdown file has YAML frontmatter, extract metadata:
import re
frontmatter = {}
content = md_content
# Check for YAML frontmatter
if md_content.startswith('---'):
match = re.match(r'^---\n(.*?)\n---\n', md_content, re.DOTALL)
if match:
import yaml
frontmatter = yaml.safe_load(match.group(1)) or {}
content = md_content[match.end():]
import markdown
md = markdown.Markdown(extensions=[
'tables',
'fenced_code',
'codehilite',
'toc', # Generates md.toc with table of contents HTML
'meta',
])
html_content = md.convert(content)
# Extract table of contents (generated by toc extension)
toc_html = md.toc # Contains <div class="toc">...</div>
Merge frontmatter with command-line options (options take precedence):
from datetime import date
context = {
**frontmatter, # From YAML frontmatter
**options, # From command arguments
'content': html_content,
'toc': toc_html, # Table of contents
}
# Set defaults
if 'title' not in context:
context['title'] = md_path.stem.replace('-', ' ').title()
if 'date' not in context:
context['date'] = date.today().isoformat()
import shutil
# Determine output folder path
if 'output' in options:
output_dir = Path(options['output'])
else:
output_dir = md_path.parent / md_path.stem # Same location, named after file
if not output_dir.is_absolute():
output_dir = BASE_DIR / output_dir
# Create output folder
output_dir.mkdir(parents=True, exist_ok=True)
from jinja2 import Environment, FileSystemLoader
templates_dir = Path("/Users/john/Documents/Workspace/2Lines/knowledge-base/_templates")
template_path = templates_dir / template_name
env = Environment(loader=FileSystemLoader(str(template_path)))
template = env.get_template('base.html')
output_html = template.render(**context)
# Write HTML
(output_dir / 'index.html').write_text(output_html)
# Copy CSS and image assets from template
for asset in template_path.glob('*'):
if asset.suffix in ['.css', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.ico']:
shutil.copy2(asset, output_dir / asset.name)
#!/usr/bin/env python3
"""Render markdown to HTML folder with assets."""
import re
import shutil
from pathlib import Path
from datetime import date
import markdown
from jinja2 import Environment, FileSystemLoader
BASE_DIR = Path("/Users/john/Documents/Workspace/2Lines/knowledge-base")
TEMPLATES_DIR = BASE_DIR / "_templates"
# Asset file extensions to copy from template
ASSET_EXTENSIONS = {'.css', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf'}
def export_to_html(
markdown_file: str,
template_name: str = "2lines-external",
output_dir: str = None,
**options
) -> Path:
"""
Export a markdown file to an HTML folder with assets.
Args:
markdown_file: Path to markdown file (relative to BASE_DIR or absolute)
template_name: Template directory name
output_dir: Output folder path (optional)
**options: Metadata options (title, author, client, etc.)
Returns:
Path to the output folder
"""
# Resolve markdown path
md_path = Path(markdown_file)
if not md_path.is_absolute():
md_path = BASE_DIR / md_path
if not md_path.exists():
raise FileNotFoundError(f"Markdown file not found: {md_path}")
# Read markdown content
md_content = md_path.read_text()
# Parse YAML frontmatter if present
frontmatter = {}
content = md_content
if md_content.startswith('---'):
match = re.match(r'^---\n(.*?)\n---\n', md_content, re.DOTALL)
if match:
try:
import yaml
frontmatter = yaml.safe_load(match.group(1)) or {}
except:
pass
content = md_content[match.end():]
# Convert markdown to HTML
md = markdown.Markdown(extensions=[
'tables',
'fenced_code',
'codehilite',
'toc',
])
html_content = md.convert(content)
# Extract table of contents
toc_html = md.toc
# Build context (frontmatter, then options override)
context = {
**frontmatter,
**{k: v for k, v in options.items() if v is not None},
'content': html_content,
'toc': toc_html,
}
# Set defaults
if 'title' not in context:
context['title'] = md_path.stem.replace('-', ' ').title()
if 'date' not in context:
context['date'] = date.today().isoformat()
# Determine output folder
if output_dir:
out_dir = Path(output_dir)
if not out_dir.is_absolute():
out_dir = BASE_DIR / out_dir
else:
out_dir = md_path.parent / md_path.stem
# Create output folder
out_dir.mkdir(parents=True, exist_ok=True)
# Load and render template
template_path = TEMPLATES_DIR / template_name
if not template_path.exists():
raise FileNotFoundError(f"Template not found: {template_path}")
env = Environment(loader=FileSystemLoader(str(template_path)))
template = env.get_template('base.html')
output_html = template.render(**context)
# Write HTML
(out_dir / 'index.html').write_text(output_html)
# Copy assets from template
assets_copied = []
for asset in template_path.iterdir():
if asset.is_file() and asset.suffix.lower() in ASSET_EXTENSIONS:
shutil.copy2(asset, out_dir / asset.name)
assets_copied.append(asset.name)
return out_dir, assets_copied
When the user invokes /export-to-html, execute:
markdown and jinja2 are installed| Error | Response |
|-------|----------|
| File not found | Report error with suggestions for correct path |
| Template not found | List available templates |
| Missing dependencies | Show pip install markdown jinja2 pyyaml |
| Render error | Show Jinja2 error message |
After successful export:
Export Complete
Source: clients/Circuit/proposal.md
Template: 2lines-external
Output folder: clients/Circuit/proposal/
Files created:
- index.html
- style.css
- logo.svg
Metadata:
Title: Project Proposal
Client: Circuit
Date: 2026-02-12
Classification: Confidential
Open in browser: open clients/Circuit/proposal/index.html
---
title: My Document
author: John Smith
client: Acme Corp
---
index.html file in a browser to preview_templates/ for different stylestools
Generate FinOps cost comparison report using cloud-doctor MCP for Suncorp Azure subscriptions
tools
Query, retrieve, and save Granola meeting notes and summaries locally using the proofgeist Granola MCP server (local cache-based). Use this skill whenever the user wants to: search or find past meetings, get meeting summaries or transcripts, copy meeting notes to local markdown files, review what was discussed in a meeting, look up action items or decisions from meetings, export meeting data, or analyze meeting patterns. Triggers: "meeting notes", "meeting summary", "what was discussed", "find the meeting", "copy meeting notes", "export meeting", "meeting transcript", "action items from meeting", "last meeting", "recent meetings", "meeting with [person]", "granola".
tools
Backup today's Granola meeting notes to local markdown files, organized by client folder. Uses Granola folder metadata to route meetings to appropriate client directories. Run this as part of an evening workflow to archive meeting notes, summaries, and action items. Triggers: "backup meetings", "save today's meetings", "archive meetings", "evening backup", "download meeting notes", "sync meetings", "meeting backup".
tools
Search, retrieve, and index local markdown knowledgebase files using the QMD CLI. Use this skill whenever the user wants to: search a local knowledgebase of markdown files, retrieve specific documents or passages from indexed collections, index or re-index a directory of .md files, check the status of the knowledge index, or perform any knowledge retrieval task against local documentation. Triggers: "search the knowledgebase", "find in docs", "index my files", "look up", "search notes", "retrieve document", "knowledge search", "what does the docs say about", "find information about", "re-index", "update the index".