slide-cli
v1.0.9
Published
CLI to create beautiful slide cards (9:16, 16:9, 1:1) from JSON + HTML templates
Maintainers
Readme
slide-cli
A TypeScript CLI to create beautiful slide cards from JSON data + HTML templates. Supports 9:16 (Stories/Reels), 16:9 (presentations/YouTube), and 1:1 (feed) aspect ratios.
slide create --data ./templates/minimal/sample.json --template minimal --out ./output
slide list [--verbose]
slide add-template <path> [--force]Installation
From npm (end users)
npm install -g slide-cli
slide --helpRequires: Node.js ≥ 18 · Chrome/Chromium (bundled via Puppeteer)
From source (contributors)
git clone https://github.com/doum1004/slide-cli.git
cd slide-cli
bun install # Bun is required for building and running tests
bun run build # outputs to dist/
npm link # exposes the `slide` command globally
slide --helpRequires: Bun ≥ 1.0 (build + test only) · Node.js ≥ 18 (runtime)
Why both Bun and Node?
The published dist/ runs on Node — so any end user with Node can install and use slide via npm without touching Bun. Bun is only needed locally to build (bun build) and run tests (bun test). This avoids the Windows issue where npm link detected Bun and generated a .ps1 wrapper that caused Puppeteer to hang silently.
Quick start (from source)
bun install
bun run build
slide list --verbose
slide create --data ./templates/minimal/sample.json --template minimal Commands
slide create
slide create --data ./templates/minimal/sample.json --template minimal --out ./output| Flag | Default | Description |
|---|---|---|
| -d, --data <file> | (required) | Path to data JSON |
| -t, --template <id> | (required) | Template id or name |
| -o, --out <dir> | ./output | Output directory |
| -f, --format <png\|jpg> | jpg | Screenshot format |
| --no-images | off | Skip screenshots (HTML only) |
| --allow-missing-images | off | Render slides without unresolvable image slots instead of aborting |
Output:
output/
├── slide-1.html slide-1.jpg
├── slide-2.html slide-2.jpg
│ …
├── index.html ← presentation viewer
├── data.json ← copy of your input
└── manifest.json ← machine-readable index (slideIndex, htmlPath, imagePath)Backends and agents should read manifest.json to get the ordered image paths:
const manifest = JSON.parse(fs.readFileSync(`${outDir}/manifest.json`, "utf-8"));
const images = manifest.slides
.filter(s => s.imagePath)
.map(s => path.join(outDir, s.imagePath));slide list
slide list
slide list --verbose # full slot schemaslide add-template
slide add-template ./my-template/
slide add-template ./my-template/ --forceData JSON format
{
"title": "My Presentation",
"slides": [
{
"layout": "minimal",
"heading": "Less is more",
"body": "Simplicity is the ultimate sophistication.",
"label": "Chapter 01",
"accent": "#c8b89a",
"bg": "#0f0e0c"
}
]
}Built-in templates
| id | Ratio | Required slots | Style |
|---|---|---|---|
| minimal | 9:16 | heading | Dark typographic, Fraunces serif |
| bold-title | 9:16 | title | Gradient editorial, Bebas Neue |
| quote-card | 9:16 | quote | Light serif pull-quote card |
| minimal-wide | 16:9 | heading | Dark typographic, two-column layout |
| bold-title-wide | 16:9 | title | Gradient editorial, title left / subtitle right |
| quote-card-wide | 16:9 | quote | Light serif pull-quote, quote left / attribution right |
Creating a custom template
my-template/
├── template.json ← manifest + slot definitions
└── template.html ← Handlebars HTML at your chosen dimensionstemplate.json
Set aspectRatio, width, and height to match your target format:
| aspectRatio | width | height | Use case |
|---|---|---|---|
| "9:16" | 1080 | 1920 | Instagram Stories, TikTok, Reels |
| "16:9" | 1920 | 1080 | YouTube thumbnails, presentations |
| "1:1" | 1080 | 1080 | Instagram feed, Twitter/X |
{
"name": "My Template",
"id": "my-template",
"version": "1.0.0",
"description": "Short description",
"aspectRatio": "16:9",
"width": 1920,
"height": 1080,
"slots": [
{ "id": "headline", "type": "text", "label": "Headline", "required": true },
{ "id": "bg", "type": "color", "label": "Background", "required": false, "default": "#fff" }
]
}Slot types: text · color · image · number · url
template.html
Match width and height in CSS to the values in your manifest:
<!DOCTYPE html><html>
<head><style>
html, body { width: 1920px; height: 1080px; background: {{bg}}; }
</style></head>
<body>
<h1>{{headline}}</h1>
{{#if subtitle}}<p>{{subtitle}}</p>{{/if}}
<footer>{{slideIndex}} / {{totalSlides}}</footer>
</body></html>Always available: {{slideIndex}} · {{totalSlides}} · {{title}}
Helpers: {{#if}} · {{upper val}} · {{lower val}} · {{default val "fallback"}}
Presentation viewer
The generated index.html has:
- ← → keys or buttons · Space = autoplay · F = fullscreen
- Home/End · touch/swipe support
- Speed selector · progress bar · dot nav
Template storage
| Path | Purpose |
|---|---|
| ~/.slide-cli/templates/ | User templates (via add-template) |
| <install>/dist/templates/ | Built-in templates (shipped with the package) |
User templates take priority over built-ins on id collision.
Docker
Production (installs from npm):
docker build -t slide-cli .
docker run --rm -v $(pwd)/output:/work/output slide-cli \
slide list
docker run --rm -v $(pwd)/output:/work/output slide-cli \
slide create --data ./templates/minimal/sample.json --template minimalLocal dev (installs from local build):
bun run build
docker build -f Dockerfile.dev -t slide-cli-dev .