Course Ebook Publishing logo

Course Ebook Publishing

Community
kevintsai1202
course-ebook-publishing

Use this skill ONLY AFTER a teaching site is feature-complete and stable, when the user wants to produce a printable / archivable / shareable version — typically a PDF ebook with cover, page numbers, table of contents, or a DOCX for editorial review. Triggers on phrases like "做電子書", "產 PDF", "ebook", "PDF ebook", "印給學員", "課程講義 PDF", "course handbook", "DOCX deliverable", "book-style layout", "build:ebook". Do NOT invoke during early site development — this is a downstream consumer that breaks if the site changes underneath. The skill produces PDF via Playwright `page.pdf()` and DOCX via pandoc, both from a single composed markdown source.

Overview

Publisherkevintsai1202
Repositoryteaching-site-skills
Skill namecourse-ebook-publishing
Stars
52
Forks
10
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by kevintsai1202 on GitHub. Read the source before you install it.

Installation

Install the Course Ebook Publishing AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/kevintsai1202/teaching-site-skills/tree/main/course-ebook-publishing
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/kevintsai1202/teaching-site-skills.git /tmp/teaching-site-skills
mkdir -p .claude/skills
cp -r /tmp/teaching-site-skills/course-ebook-publishing .claude/skills/course-ebook-publishing
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Course Ebook Publishing in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Course Ebook Publishing on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Course Ebook Publishing is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Course Ebook Publishing

Schema authority: this skill reads the live window.COURSE object whose shape is defined in _shared/domain-primitives.md. Quiz / pre-test / post-test items are filtered OUT of the ebook (per §10 quiz item rules + ebook content policy).

Filename convention (English-first): outputs land in dist/{name}.pdf / dist/{name}.docx. Source markdown is composed under dist/master.md.

This skill turns a finished teaching website into a book — PDF (primary) and optionally DOCX (for editorial review or further authoring). It is a post-site step: it consumes window.COURSE from the live site, never re-authoring content.

When to Invoke

  • Site is feature-complete (Stages 1–5 done) and content is stable.
  • Stakeholders ask for a printable hand-out, an archivable record, or a deliverable for non-web channels.
  • Sometimes invoked after course-corporate-edition to produce a client deliverable bundle.

Do NOT invoke when site is mid-development. The ebook pipeline reads window.COURSE; if the site changes daily, the ebook drifts daily.

Architecture: Single Source → Two Outputs

window.COURSE  ─┐
                ├─→ compose-ebook.mjs ─→ master.md ─→ pandoc ─→ ebook.docx
asset folders ─┤                                  └─→ Playwright page.pdf() ─→ ebook.pdf
materials/   ─┘

Why a single composed markdown intermediate: keeps PDF and DOCX content identical. Pandoc handles markdown → DOCX cleanly; Playwright renders the same markdown via an HTML wrapper → PDF. Two outputs, one source of truth.

File Layout (Standard)

scripts/
├── build-ebook.mjs              ← CLI entry point (--md-only, --no-docx, --no-pdf, --output, --keep-html)
├── render-pdf.mjs               ← markdown → HTML → PDF via Playwright
└── lib/
    ├── compose-ebook.mjs        ← loadSources + composeXxx functions (cover, TOC, chapters, appendix)
    └── reference.docx           ← pandoc style template (generated by gen-reference-docx.mjs)

style-ebook.css                   ← @page rules, cover, page-number footer, print-only styles
dist/                             ← output: ebook.md, ebook.pdf, ebook.docx

Loading Source Data: window.COURSE from index.html

When the site uses external course-data.js, just import it. When it uses inlined COURSE (corporate edition), extract via regex + vm sandbox:

js
import vm from 'node:vm';
async function loadCourseFromIndexHtml(htmlPath) {
  const html = await fs.readFile(htmlPath, 'utf8');
  const match = html.match(/<script>\s*window\.COURSE\s*=\s*\{[\s\S]*?\};\s*<\/script>/);
  if (!match) throw new Error('window.COURSE 區塊找不到');
  const code = match[0].replace(/<\/?script>/g, '');
  const sandbox = { window: {}, console };
  vm.createContext(sandbox);
  vm.runInContext(code, sandbox);
  return sandbox.window.COURSE;
}

Why vm sandbox not eval: vm isolates the script's globals from the build process; an accidental side-effect can't leak.

PDF Rendering: Playwright page.pdf() (not Chrome CLI)

Original implementation: pandoc → HTML → msedge --headless --print-to-pdf. Switch to Playwright — Chrome CLI doesn't support footerTemplate (no page numbers).

js
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(`file://${path.resolve(htmlPath)}`, { waitUntil: 'networkidle' });
await page.pdf({
  path: outputPath,
  format: 'A4',
  printBackground: true,
  displayHeaderFooter: true,
  headerTemplate: '<div></div>',
  footerTemplate: `
    <div style="font-size:9pt; width:100%; text-align:center; color:#666;">
      <span class="pageNumber"></span> / <span class="totalPages"></span>
    </div>`,
  margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
});
await browser.close();

Cover page without page number

The cover should be number-less. CSS:

css
@page { margin: 20mm; }
@page :first { margin: 0; }    /* full bleed cover */

Playwright respects @page :first — the footer doesn't print on it.

Book-Style Layout (style-ebook.css)

The example workshop's "book-grade typography" commit was driven by aesthetics, not just formality. Key rules worth porting:

css
/* 章首跨頁分頁 */
h1.chapter { page-break-before: always; }

/* 圖文並排(Grid 排版) */
.figure-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12mm;
}
.figure-grid .three-up { grid-template-columns: repeat(3, 1fr); }

/* 提示詞區塊:橘色 PROMPT 徽章 + 暗底 code */
.prompt-block {
  background: #1a1a1a; color: #e0e0e0;
  border-left: 4px solid #ff8c00;
  padding: 12pt; margin: 8pt 0;
}
.prompt-block::before {
  content: 'PROMPT'; display: inline-block;
  background: #ff8c00; color: white;
  padding: 2pt 8pt; border-radius: 3pt;
  font-size: 8pt; margin-bottom: 6pt;
}

/* 任務勾選方框(純印刷) */
.task::before {
  content: '☐'; margin-right: 8pt;
  font-size: 14pt; color: #888;
}

/* 學習目標綠色徽章 */
.goal::before {
  content: '✓'; margin-right: 6pt;
  color: #2e7d32; font-weight: bold;
}

DOCX via pandoc + reference template

js
const args = [
  mdPath,
  '-f', 'gfm+attributes+raw_html',
  '-t', 'docx',
  '--standalone',
  '--toc', '--toc-depth=2',
  '--resource-path', path.dirname(mdPath),
  '--reference-doc', 'scripts/lib/reference.docx',
  '-o', outPath,
];
spawn('pandoc', args);

reference.docx is a one-time-generated template with your chosen fonts (e.g. Microsoft JhengHei UI for Chinese), heading colors, paragraph spacing. Generate via scripts/gen-reference-docx.mjs — pandoc copies the styles.

Caveat: raw HTML support in DOCX is partial. <figure-grid> blocks won't render as grids in Word — they degrade to vertical stack. Use markdown ![alt](path) syntax for images so they survive both formats.

Compose Module Anatomy

compose-ebook.mjs exports one function per chapter type:

js
export async function loadSources() { /* read COURSE + materials + assets */ }
export async function composeCover(meta) { /* big title, day list, meta card */ }
export async function composeOverview(meta) { /* TOC + course overview */ }
export async function composeChapter(day, assetIndex) { /* hero image + units */ }
export async function composeAppendixMaterials(materials) { /* full-text material appendix */ }
export async function composeAppendixSkills(skills) { /* QR-coded skill links */ }

Each returns a markdown string. build-ebook.mjs concatenates them with \n\n---\n\n.

Asset Path Strategy

Inside the composed markdown, images use paths relative to the markdown file (./assets/illustrations/foo.png). Pandoc's --resource-path resolves them; Playwright's file:// HTML loader does too. No absolute paths.

For corporate editions with asset fallback, resolve the actual file location at compose time and embed the resolved path:

js
async function pickAsset(name, roots) {
  for (const root of roots) {
    const full = path.join(root, name);
    try { await fs.access(full); return path.relative(mdDir, full); }
    catch { continue; }
  }
  return null;  // caller decides whether to drop the image or fall back to placeholder
}

What to Filter Out

Some content lives on the website but should not be in the ebook:

  • Quiz items: leaking exam questions defeats the assessment. The example workshop filters out the entire quiz chapter.
  • Pre-test / post-test sections: same reason.
  • Interactive-only features: "點擊複製" buttons make no sense in print. Render the raw prompt text, not the button.
  • Dynamic illustrations: anything generated client-side via canvas/JS won't render.

Add filter conditions to the compose functions; don't try to render-then-strip.

Verification

After generating, verify:

js
// scripts/verify-ebook.mjs
// - PDF file exists and is > 100KB
// - First page (cover) has no page number
// - TOC pages have entries
// - Every image reference resolved (no broken-image placeholders)

For DOCX, open in Word once and visually check: TOC linked, fonts applied, no missing-image squares.

CLI Conventions

The example workshop's build:ebook script supports:

FlagBehavior
(no flag)Build both PDF + DOCX
--md-onlyStop after composing markdown (debug content)
--no-docxSkip DOCX (faster iteration on PDF)
--no-pdfSkip PDF (faster iteration on DOCX)
--keep-htmlDon't delete intermediate HTML (debug CSS)
--output PATHCustom PDF output path

Provide these from day one — you'll iterate the layout many times.

Anti-Patterns

  • Re-authoring content in the ebook builder — the ebook is a derivative. If you find yourself adding new prose in the compose functions, that prose belongs in course-content-authoring instead.
  • Using Chrome CLI for PDF — switch to Playwright early; you'll want footers eventually.
  • Hardcoding image paths — use pickAsset() with a list of roots, especially when supporting corporate edition fallbacks.
  • Forgetting @page :first { margin: 0 } — cover ends up with a stray page number in the footer.
  • Re-running the ebook build every time the site changes — only build at stable checkpoints; the intermediate dist/ folder is large.

Hand-off

When this skill finishes:

  • dist/{course-name}.pdf and optionally .docx are produced.
  • Verify scripts pass.
  • If for a client, the file goes into the deliverable bundle.

Tell the user: "ebook ready. The site and ebook are now in sync; from now on, treat the site as canonical and rebuild the ebook only at release milestones — don't edit the ebook directly, it'll get overwritten."

Frequently asked questions

What does the Course Ebook Publishing AI skill do?

Use this skill ONLY AFTER a teaching site is feature-complete and stable, when the user wants to produce a printable / archivable / shareable version — typically a PDF ebook with cover, page numbers, table of contents, or a DOCX for editorial review. Triggers on phrases like "做電子書", "產 PDF", "ebook", "PDF ebook", "印給學員", "課程講義 PDF", "course handbook", "DOCX deliverable", "book-style layout", "build:ebook". Do NOT invoke during early site development — this is a downstream consumer that breaks if the site changes underneath. The skill produces PDF via Playwright `page.pdf()` and DOCX via pan...

Why use Course Ebook Publishing on TypingMind?

Because you install it once and use it with any model. Course Ebook Publishing is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Course Ebook Publishing in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/kevintsai1202/teaching-site-skills/tree/main/course-ebook-publishing. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Course Ebook Publishing?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Course Ebook Publishing?

As many as you like. As long as a model supports skills, you can use Course Ebook Publishing with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Course Ebook Publishing AI skill free?

Yes. It is published on GitHub by kevintsai1202 under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇