@bendyline/squisq-cli

中文文档
2.9.30发布于 2026/11/08MITTypeScriptES Module构建来源已验证近 30 天下载 7188

Squisq CLI — convert documents and render MP4 video or animated GIF

原始使用文档
文档目录19 节
  1. Install
  2. Commands
  3. squisq convert <input>
  4. squisq video <input> [output]
  5. squisq image <input> [output]
  6. squisq doctor
  7. squisq validate <input>
  8. squisq transform <input>
  9. Input Formats
  10. Programmatic API
  11. convert()
  12. renderDocToMp4
  13. renderDocToGif
  14. renderDocToDashboardPng
  15. Native frame encoding
  16. extractThumbnails
  17. Other exports
  18. Related Packages
  19. License

@bendyline/squisq-cli

Command-line tool and programmatic API for converting Squisq documents between DOCX, PPTX, PDF, XLSX, CSV, HTML, EPUB, Markdown, and container ZIP — and rendering them to MP4 video or animated GIF. Reads Markdown, binary documents (.docx/.pptx/.pdf/.xlsx/.csv/.html), ZIP/.dbk containers, folders, or pre-built Doc JSON as input.

Part of the Squisq monorepo.

npm MIT License

Install

npm install -g @bendyline/squisq-cli

For squisq video (and convert to mp4 or gif) you also need:

  • ffmpeg — resolved from the SQUISQ_FFMPEG env var, then your PATH (brew install ffmpeg / apt install ffmpeg / winget install ffmpeg), then an optionally-installed ffmpeg-static package.
  • A Playwright-managed Chromium for headless frame capture (e.g. npx playwright install chromium)

Run squisq doctor to check the video toolchain. Document conversion to non-video formats has no native requirements.

Commands

squisq convert <input>

Convert a document to one or more formats. Input can be Markdown or a binary document (.docx, .pptx, .pdf, .xlsx, .csv, .html), a .zip/.dbk container, or a folder. Conversion goes through Squisq's MarkdownDocument pivot model and preserves most of the flavor of the document (structure, formatting, tables, images) — it is not a lossless round-trip.

squisq convert input.md -o report.docx           # single file; format inferred from extension
squisq convert report.docx -o report.md           # binary input → markdown
squisq convert input.md --formats pptx             # single format to the default output dir
squisq convert input.md --formats docx,pptx,pdf    # multiple formats
squisq convert project.dbk --output-dir ./out --formats html,docx
squisq convert input.md --theme cinematic --transform magazine --formats pptx
OptionDescriptionDefault
-o, --output <file>Single output file; format inferred from its extension (cannot combine with --formats)—
-d, --output-dirOutput directory (multi-format mode)same as input
-f, --formatsComma-separated: docx, pptx, pdf, html, htmlzip, epub, dbk, md, xlsx, csv, mp4, gifdefault set
--overwriteReplace existing output files (otherwise the run refuses and exits non-zero)off
-t, --themeSquisq theme id — built-in or a custom theme inlined in the doc's frontmatternone
--transformTransform style applied before export (e.g. documentary, magazine, minimal)none
--no-auto-templatesDisable content-aware template auto-picking for unannotated headings(auto on)

v1.5 breaking flag change: -o is now a single-file destination (format inferred from the extension). The old -o output-directory behavior is now -d, --output-dir. A bare convert <input> with no -o/--formats writes a default set that deliberately excludes md/xlsx/csv/mp4/gif.

Notes:

  • Existing files are never overwritten by default. A bare convert report.md writes several files next to the input; if any already exists the run refuses up front (before converting anything) and exits 1. Pass --overwrite to replace them. The flag is --overwrite, not -f, because -f is --formats. There is no prompt — this stays scriptable.
  • An unknown --formats id is a hard error. -f docx,pfd fails with Unknown format: "pfd" (did you mean "pdf"?) and produces nothing, rather than warning and exiting 0 with the PDF silently missing.
  • html produces a single self-contained file with the standalone player inlined (static mode); htmlzip produces a <name>.html.zip archive with external assets and optional audio.
  • epub embeds images from the input container and, when the doc has narration segments, generates EPUB 3 Media Overlays.
  • xlsx export is tables-only; csv export emits the first table; mp4 and gif require the render toolchain (see Install).
  • dbk re-serializes the input container as a ZIP.

squisq video <input> [output]

Render a document to MP4 or animated GIF. Playwright captures deterministic frames from a headless player page; native ffmpeg encodes H.264 + AAC for MP4 or a generated global palette for GIF. Documents with Mermaid fences automatically use the full standalone player so diagrams are captured; other documents keep the smaller light player. GIF has no audio track.

squisq video input.md output.mp4
squisq video input.md output.gif
squisq video input.md --format gif --no-animations
squisq video project.dbk --quality high --fps 30
squisq video ./my-folder --orientation portrait --captions social
squisq video input.md -t documentary --transform magazine --cover-preroll 1.5
squisq video doc.json -o out.mp4
OptionDescriptionDefault
-o, --outputOutput .mp4 or .gif path; extension selects the format<input>.mp4
--formatmp4 or gifinferred, otherwise mp4
--fpsFrames per second (MP4 1–120; GIF 1–100)MP4 30; GIF 10
--qualityMP4-only quality: draft, normal, or highnormal
--orientationlandscape or portraitlandscape
--captionsoff, standard, or socialMP4 off; GIF standard
--animations / --no-animationsEnable/disable layer animations and block transitionsMP4 enabled; GIF disabled
--loopGIF repeat count (0 forever, -1 no loop)0
--max-colorsGIF palette size, 2–256256
--ditherGIF dithering: bayer, sierra2_4a, or nonesierra2_4a
--bayer-scaleOrdered Bayer strength, 0–53
-t, --themeSquisq theme id to applynone
--motionMotion profile: calm, documentary or vibrantdoc's, then theme's
--transformTransform style to apply before renderingnone
--cover-prerollSeconds of cover-slide pre-roll before the story starts2
--width / --heightDimension overrides in pixels — must be evenMP4 1080p; GIF 960×540
--overwriteReplace an existing output file (otherwise refuse and exit non-zero)off
--no-auto-templatesDisable content-aware template auto-picking for unannotated headings(auto on)
--frame-transportMP4 frame delivery: pipe (stream stills to ffmpeg) or memorypipe
--capture-formatMP4 still captured per frame: png or jpeg (pipe only)high png, else jpeg
--frames-dirMP4 only: spool every captured still here and keep it after the runnone
--resumeMP4 only: reuse stills an identical render spooled in --frames-diroff

Notes:

  • Dimensions must be even. --width 851 is rejected immediately — before the document is read or a browser launches — with Video width must be an even number of pixels (got 851) … Use 850 or 852. Odd values are rejected rather than rounded, so a render never silently ships at a size you did not ask for. The rule is the same for MP4 and GIF, and matches browser export.
  • An existing output file is never overwritten by default. The check runs before rendering, so a colliding path costs you a second rather than a full capture. Pass --overwrite to replace.
  • FFmpeg failures report the one relevant line (e.g. [libx264] width not divisible by 2 (851x480) (exit code 1)) rather than dumping the whole command line and stderr buffer.
  • MP4 frames stream to ffmpeg as they are captured. Memory stays flat for a story of any length; --frame-transport memory restores the old retain-then-encode path, which is bounded to a few seconds of 1080p photo slides. --frames-dir keeps every still (plus a manifest.json naming the doc, size, rate, captions and animation settings), and --resume skips re-capturing stills that an identical render already spooled there.
  • Portrait and custom sizes are composed natively. The render page pins the player's viewport to the export size; a player bundle that ignores it fails the run before capture instead of producing a letterboxed landscape layout.

squisq image <input> [output]

Render a document's Dashboard rendition — every block arranged on one canvas — to a PNG image. Needs Playwright Chromium only (no ffmpeg; this is a single-frame capture, not a video encode). The layout comes from the doc's squisq-dashboard-layout frontmatter (or --layout), defaulting to the auto-pick that chooses the smallest built-in layout fitting the block count; the document-title band renders by default and --no-title hides it. --style picks the cell dressing (basic, card, panel, accent — all theme-derived), defaulting to the doc's squisq-dashboard-style.

squisq image input.md output.png
squisq image input.md --resolution 4k
squisq image input.md --resolution square --layout grid-2x2
squisq image input.md --style card
squisq image input.md --width 1280 --height 720 --no-title
squisq image input.md -t documentary --transform magazine
OptionDescriptionDefault
-o, --outputOutput .png path<input>.png
--resolutionNamed preset: hd, fhd, 4k, square, square-2k, portrait, portrait-4k, standardfhd
--width / --heightCustom pixels (both required together; excludes --resolution). 64–7680 per axis, ≤33 MP; odd values finepreset
--layoutDashboard layout id (built-ins or a doc-defined custom layout), or autoauto
--styleCell style variant: basic, card, panel, accent (all theme-derived)doc setting
--title / --no-titleShow/hide the document-title bandshown
--formatOutput format (png)png
-t, --themeSquisq theme id to applynone
--transformTransform style to apply before renderingnone
--overwriteReplace an existing output file (otherwise refuse and exit non-zero)off
--no-auto-templatesDisable content-aware template auto-picking for unannotated headings(auto on)

The same export is available through the registry as squisq convert <input> -f png.

squisq doctor

Preflight the rendering toolchain: reports the resolved ffmpeg path, version, and which source it came from (SQUISQ_FFMPEG env / PATH / ffmpeg-static) with an install hint when missing, attempts a headless Chromium launch, reports the Node version, and summarizes per-feature readiness (video/GIF need ffmpeg + Chromium; the dashboard PNG image needs Chromium only).

squisq doctor

squisq validate <input>

Structurally validate a .md file, .zip/.dbk container, or folder. Reports unknown templates (with did-you-mean), unparsed {[…]} annotations, malformed heading attributes, unresolved connections, duplicate ids, bad data fences, and missing asset references — with line numbers.

squisq validate input.md
squisq validate project.dbk --json
squisq validate ./my-folder --strict
OptionDescription
--jsonEmit diagnostics as machine-readable JSON
--strictExit non-zero on warnings too

Diagnostics are reported at three severities — error, warning, and info (info is counted and shown separately). Exit codes depend on errors only: 0 clean, warnings-only, or info-only; 1 errors (or any warning with --strict); 2 input unreadable.

squisq transform <input>

The condense operation compacts table padding and decorative dividers while retaining table data, alignment, code, citations, and attribution. It verifies structural equivalence before returning. When combining operations, put condense after cleanup so serialization does not reintroduce padding.

Apply one-time markdown source transforms to a .md file, in the order given: unwrap (remove forced line wrapping so each paragraph is one line — hard breaks kept), wrap (hard-wrap paragraph prose at a column width on word boundaries — code, tables, and headings untouched), and cleanup (canonical house-style normalization: bullets, emphasis, headings, table padding, spacing; annotations and frontmatter preserved). Not to be confused with the --transform <style> slideshow-style flag on convert/video — this command rewrites the markdown text itself.

The transformed markdown goes to stdout by default (status messages go to stderr, so it pipes cleanly).

squisq transform doc.md --ops unwrap
squisq transform doc.md --ops unwrap,cleanup > cleaned.md
squisq transform doc.md --ops wrap --width 100 --in-place
squisq transform doc.md --ops cleanup -o cleaned.md
squisq transform doc.md --ops condense -o compact.md
OptionDescription
--ops <list>Comma-separated transforms, applied in order (unwrap, wrap, cleanup)
--width <n>Column width for wrap (20–500, default 80)
-o <file>Write the result to a file (refuses to overwrite without --overwrite)
--in-placeRewrite the input file (conflicts with -o)
--overwriteAllow -o to replace an existing file

Transforms run in strict mode: each one reparses its output and structurally compares it against the input document; if equivalence cannot be proven, the command exits 1 without emitting anything. Exit codes: 0 success, 1 transform/output failure, 2 input unreadable.

Input Formats

All commands accept:

InputDescription
.md filePlain Markdown file
Binary docs.docx, .pptx, .pdf, .xlsx, .csv, .html (convert)
.zip/.dbkContainer archive with document + embedded media
FolderDirectory with a Markdown file and associated media files
.json filePre-built Doc JSON (video only)

Programmatic API

Import @bendyline/squisq-cli/api to use the same pipeline as a library from Node.js — no shell-out required. Like the CLI, renderDocToMp4 and renderDocToGif require ffmpeg and a Playwright-managed Chromium.

convert()

A pre-bound wrapper over @bendyline/squisq-formats' convert() that injects the CLI's format registry (every built-in exporter plus the CLI-only mp4 and gif formats) and a default resolvePlayerScript (so HTML/player-embedding exports work out of the box). Both are overridable via options. createCliRegistry() returns that same registry for direct use.

import { convert, createCliRegistry, ConversionError } from '@bendyline/squisq-cli/api';

const result = await convert({ kind: 'markdown', markdown: '# Hello' }, 'docx');
// result.bytes (Uint8Array), result.mimeType, result.suggestedFilename, result.warnings

ConversionError (with a stable code) and the ConvertSource / ConvertOptions / ConversionResult / FormatRegistry types are re-exported for convenience.

renderDocToMp4

import { renderDocToMp4, readInput } from '@bendyline/squisq-cli/api';

// Load a document from disk (.md, .zip/.dbk, folder, or Doc .json)
const input = await readInput('./my-article.md');

// `doc` is always normalized and ready to render. `markdownDoc` is present
// only when the source was markdown-shaped; `sourceFormat` identifies the input.
const { doc, container, markdownDoc, sourceFormat } = input;

// Render to MP4
const result = await renderDocToMp4(doc, container, {
  outputPath: './output.mp4',
  fps: 30, // default 30
  quality: 'high', // 'draft' | 'normal' | 'high' (default 'normal')
  orientation: 'landscape', // 'landscape' | 'portrait' (default 'landscape')
  captionStyle: 'social', // 'off' | 'standard' | 'social' (default 'off')
  animationsEnabled: false, // static slide changes; media/timing remain active
  coverPreRoll: 2, // seconds of cover-slide pre-roll (default 0)
  frameTransport: 'pipe', // 'pipe' (default; streams stills to ffmpeg) | 'memory'
  captureFormat: 'png', // 'png' | 'jpeg' (default: png for high quality, jpeg otherwise)
  framesDir: './frames', // optional: keep every still + manifest; with resume: true, reuse them
  onFrame: ({ index, totalFrames, captureMs, reused }) => {}, // per-frame throughput hook
  onProgress: (phase, pct) => console.log(`${phase}: ${pct}%`),
});
// result: { duration, frameCount, outputPath, reusedFrameCount?, framesDir? }

console.log(`Rendered ${result.frameCount} frames (${result.duration}s) → ${result.outputPath}`);

The container's audio segments become the MP4's audio track; timed media clips and block videos found in the doc are embedded and mixed at their scheduled positions.

The capture page loads nothing from the network, so a theme font the rendering machine lacks (any Google Fonts stack, such as warm-earth's DM Sans body) renders in a fallback face. Pass the font files as fonts, one entry per file; every renderer here (MP4, GIF, cover and dashboard PNG) accepts them:

const fonts: RenderFontFace[] = [
  { family: 'DM Sans', data: await readFile('DMSans-Variable.woff2'), weight: '100 1000' },
  {
    family: 'DM Sans',
    data: await readFile('DMSans-VariableItalic.woff2'),
    weight: '100 1000',
    style: 'italic',
  },
];
await renderDocToMp4(doc, container, { outputPath: './output.mp4', fonts });

format defaults to 'woff2' (also 'woff', 'truetype', 'opentype'), and unicodeRange takes a CSS unicode-range for a file holding one subset.

renderDocToGif

import { renderDocToGif } from '@bendyline/squisq-cli/api';

const result = await renderDocToGif(doc, input.container, {
  outputPath: './output.gif',
  fps: 10,
  captionStyle: 'off', // override the default 'standard' captions
  animationsEnabled: false,
  maxColors: 256,
  loop: 0,
});

GIF defaults to 960×540 landscape (540×960 portrait), 10 fps, standard captions, an infinite loop, and disabled slide animations/transitions. Set captionStyle: 'off' to omit captions. Embedded video still advances, but all audio is omitted and reported through result.warnings.

renderDocToDashboardPng

import { renderDocToDashboardPng } from '@bendyline/squisq-cli/api';

const result = await renderDocToDashboardPng(doc, input.container, {
  outputPath: './dashboard.png',
  resolution: 'square', // or width/height for custom pixels
  layout: 'grid-2x2', // 'auto' (default) picks by block count
  style: 'card', // 'basic' (default) | 'card' | 'panel' | 'accent'
  title: true,
});
// result: { bytes: Uint8Array, width, height, outputPath? }

Renders the Dashboard rendition to a single PNG. Needs Playwright Chromium only — no ffmpeg. resolution names a preset from DASHBOARD_RESOLUTIONS (default fhd, 1920×1080); explicit width/height (both together) replace it. Apply a theme upstream ({ ...doc, themeId }), the same convention the video renderers use.

Native frame encoding

For callers that already have PNG frames and do not need Playwright capture, the API exposes the native FFmpeg layer directly:

import { framesToGifNativeBytes, framesToMp4NativeBytes } from '@bendyline/squisq-cli/api';

const mp4 = await framesToMp4NativeBytes('/usr/bin/ffmpeg', pngFrames, audioBytes, {
  fps: 30,
  quality: 'high',
});

const gif = await framesToGifNativeBytes('/usr/bin/ffmpeg', pngFrames, {
  fps: 10,
  width: 960,
  height: 540,
});

The path variants write to a caller-supplied output; each Bytes variant returns a Uint8Array. MP4 pads short audio with silence so narration cannot truncate the timeline. GIF uses a compression-friendly diff palette and changed-rectangle application.

extractThumbnails

Extract JPEG thumbnails from the first frame of a rendered video:

import { extractThumbnails } from '@bendyline/squisq-cli/api';

await extractThumbnails({
  videoPath: './output.mp4',
  outputDir: './thumbs',
  slug: 'my-article', // filenames: {slug}-{width}x{height}.jpg
  sizes: [
    { name: 'og', width: 1200, height: 630, filter: 'scale=1200:630' },
    { name: 'thumb', width: 480, height: 270, filter: 'scale=480:270' },
  ],
  force: false, // overwrite existing files (default false)
});

Other exports

  • readInput(inputPath) → { doc: Doc, container: ContentContainer, markdownDoc?: MarkdownDocument, sourceFormat: FormatId }
  • MemoryContentContainer (re-export from @bendyline/squisq/storage)
  • VideoQuality, VideoOrientation types (re-exports from @bendyline/squisq-video)
  • DASHBOARD_RESOLUTIONS, DEFAULT_DASHBOARD_RESOLUTION, resolveDashboardDimensions, validateDashboardImageDimensions (re-exports from @bendyline/squisq-video)

See the full API Reference for all types and options.

PackageDescription
@bendyline/squisqHeadless core — schemas, templates, markdown
@bendyline/squisq-formatsDOCX/PDF/HTML/EPUB/PPTX converters (used by convert)
@bendyline/squisq-videoHeadless video rendering foundation (used by video)
@bendyline/squisq-reactReact components + standalone player bundle

License

MIT

版本与发布记录

@bendyline/squisq-cli 共发布 91 个版本,首次发布于 2026/03/26;过去 12 个月发布了 91 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.9.30latest2026/11/08今天
2.9.292026/11/07今天
2.9.282026/11/052 天前
2.9.272026/11/053 天前
2.9.262026/11/017 天前
2.9.252026/10/308 天前

关于 @bendyline/squisq-cli 的常见问题

@bendyline/squisq-cli 是什么?

Squisq CLI — convert documents and render MP4 video or animated GIF

如何安装 @bendyline/squisq-cli?

在项目目录运行 npm install @bendyline/squisq-cli,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 squisq,可以用 npx squisq 直接运行。

@bendyline/squisq-cli 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bendyline/squisq-cli 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bendyline/squisq-cli 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.22.2 || ^24.15.0 || >=26.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bendyline/squisq-cli 可以商用吗?

@bendyline/squisq-cli 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bendyline/squisq-cli 的下载量有多少?

根据 npm 官方下载统计,@bendyline/squisq-cli 在 2026/10/08 – 2026/11/06 这 30 天内被下载 7,188 次,日均约 240 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bendyline/squisq-cli 还在维护吗?

最新版本 2.9.30 发布于 2026/11/08(今天);至今共发布 91 个版本,其中过去 12 个月发布了 91 个;首次发布于 2026/03/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。