@bendyline/squisq-cli 1.1.6 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bendyline/squisq-cli
2
2
 
3
- Command-line tool for converting Squisq documents to multiple formats and rendering them to video. Reads Markdown files, ZIP/DBK containers, or folders as input.
3
+ 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. Reads Markdown, binary documents (`.docx`/`.pptx`/`.pdf`/`.xlsx`/`.csv`/`.html`), ZIP/`.dbk` containers, folders, or pre-built Doc JSON as input.
4
4
 
5
5
  Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
6
6
 
@@ -13,70 +13,155 @@ Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
13
13
  npm install -g @bendyline/squisq-cli
14
14
  ```
15
15
 
16
+ For `squisq video` (and `convert` to `mp4`) you also need:
17
+
18
+ - 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.
19
+ - A Playwright-managed Chromium for headless frame capture (e.g. `npx playwright install chromium`)
20
+
21
+ Run `squisq doctor` to check the video toolchain. Document conversion to non-video formats has no native requirements.
22
+
16
23
  ## Commands
17
24
 
18
- ### `squisq convert`
25
+ ### `squisq convert <input>`
19
26
 
20
- Convert a Squisq document to DOCX, PPTX, PDF, HTML, EPUB, or DBK (container ZIP):
27
+ 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.
21
28
 
22
29
  ```bash
23
- squisq convert input.md --formats docx,pptx,pdf
30
+ squisq convert input.md -o report.docx # single file; format inferred from extension
31
+ squisq convert report.docx -o report.md # binary input → markdown
32
+ squisq convert input.md --format pptx # single format to the default output dir
33
+ squisq convert input.md --formats docx,pptx,pdf # multiple formats
24
34
  squisq convert project.dbk --output-dir ./out --formats html,docx
25
- squisq convert ./my-folder --formats pdf --theme documentary
26
- squisq convert input.md --theme cinematic --transform magazine --formats pptx
35
+ squisq convert input.md --theme cinematic --transform magazine --format pptx
27
36
  ```
28
37
 
29
- | Option | Description | Default |
30
- | -------------- | ------------------------------------------------------ | ----------- |
31
- | `--output-dir` | Output directory | current dir |
32
- | `--formats` | Comma-separated list: docx, pptx, pdf, html, epub, dbk | all |
33
- | `--theme` | Squisq theme ID (e.g., documentary, cinematic) | none |
34
- | `--transform` | Transform style (e.g., documentary, magazine) | none |
38
+ | Option | Description | Default |
39
+ | --------------------- | ------------------------------------------------------------------------------------------------------- | ------------- |
40
+ | `-o, --output <file>` | **Single** output file; format inferred from its extension (cannot combine with `--formats`/`--format`) | — |
41
+ | `-d, --output-dir` | Output directory (multi-format mode) | same as input |
42
+ | `-f, --formats` | Comma-separated: `docx`, `pptx`, `pdf`, `html`, `htmlzip`, `epub`, `dbk`, `md`, `xlsx`, `csv`, `mp4` | default set |
43
+ | `--format <id>` | Produce a single format (alias for a one-entry `--formats`) | |
44
+ | `-t, --theme` | Squisq theme id — built-in or a custom theme inlined in the doc's frontmatter | none |
45
+ | `--transform` | Transform style applied before export (e.g. `documentary`, `magazine`, `minimal`) | none |
46
+ | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
47
+
48
+ > **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`/`--format`/`--formats` writes a default set that deliberately excludes `md`/`xlsx`/`csv`/`mp4`.
49
+
50
+ Notes:
35
51
 
36
- ### `squisq video`
52
+ - `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.
53
+ - `epub` embeds images from the input container and, when the doc has narration segments, generates EPUB 3 Media Overlays.
54
+ - `xlsx` export is tables-only; `csv` export emits the first table; `mp4` requires the video toolchain (see Install).
55
+ - `dbk` re-serializes the input container as a ZIP.
37
56
 
38
- Render a Squisq document to MP4 video using Playwright for frame capture and native ffmpeg for encoding:
57
+ ### `squisq video <input> [output]`
58
+
59
+ Render a document to MP4: Playwright captures frames from a headless player page, native ffmpeg encodes them (H.264 + AAC when the doc has audio).
39
60
 
40
61
  ```bash
41
62
  squisq video input.md output.mp4
42
63
  squisq video project.dbk --quality high --fps 30
43
64
  squisq video ./my-folder --orientation portrait --captions social
65
+ squisq video input.md -t documentary --transform magazine --cover-preroll 1.5
66
+ squisq video doc.json -o out.mp4
67
+ ```
68
+
69
+ | Option | Description | Default |
70
+ | ---------------------- | -------------------------------------------------------------------- | --------------- |
71
+ | `-o, --output` | Output MP4 path (positional `[output]` wins over the flag) | `<input>.mp4` |
72
+ | `--fps` | Frames per second (1–120) | 30 |
73
+ | `--quality` | `draft`, `normal`, or `high` | normal |
74
+ | `--orientation` | `landscape` (1920×1080) or `portrait` (1080×1920) | landscape |
75
+ | `--captions` | `off`, `standard`, or `social` | off |
76
+ | `-t, --theme` | Squisq theme id to apply | none |
77
+ | `--transform` | Transform style to apply before rendering | none |
78
+ | `--cover-preroll` | Seconds of cover-slide pre-roll before the story starts | 2 |
79
+ | `--width` / `--height` | Dimension overrides in pixels | per orientation |
80
+ | `--no-auto-templates` | Disable content-aware template auto-picking for unannotated headings | (auto on) |
81
+
82
+ ### `squisq doctor`
83
+
84
+ Preflight the video 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, and reports the Node version.
85
+
86
+ ```bash
87
+ squisq doctor
44
88
  ```
45
89
 
46
- | Option | Description | Default |
47
- | --------------- | ------------------------- | --------- |
48
- | `--fps` | Frames per second (1–120) | 30 |
49
- | `--quality` | draft, normal, or high | normal |
50
- | `--orientation` | landscape or portrait | landscape |
51
- | `--captions` | off, standard, or social | off |
52
- | `--width` | Override width in pixels | auto |
53
- | `--height` | Override height in pixels | auto |
90
+ ### `squisq validate <input>`
54
91
 
55
- **Requires:** [ffmpeg](https://ffmpeg.org/) installed and available on your PATH.
92
+ 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.
93
+
94
+ ```bash
95
+ squisq validate input.md
96
+ squisq validate project.dbk --json
97
+ squisq validate ./my-folder --strict
98
+ ```
99
+
100
+ | Option | Description |
101
+ | ---------- | ----------------------------------------- |
102
+ | `--json` | Emit diagnostics as machine-readable JSON |
103
+ | `--strict` | Exit non-zero on warnings too |
104
+
105
+ 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.
106
+
107
+ ## Input Formats
108
+
109
+ All commands accept:
110
+
111
+ | Input | Description |
112
+ | ------------- | -------------------------------------------------------------- |
113
+ | `.md` file | Plain Markdown file |
114
+ | Binary docs | `.docx`, `.pptx`, `.pdf`, `.xlsx`, `.csv`, `.html` (`convert`) |
115
+ | `.zip`/`.dbk` | Container archive with document + embedded media |
116
+ | Folder | Directory with a Markdown file and associated media files |
117
+ | `.json` file | Pre-built Doc JSON (`video` only) |
56
118
 
57
119
  ## Programmatic API
58
120
 
59
- Use the CLI as a library from Node.js — no shell-out required:
121
+ Import `@bendyline/squisq-cli/api` to use the same pipeline as a library from Node.js — no shell-out required. Like the CLI, `renderDocToMp4` requires ffmpeg and a Playwright-managed Chromium.
122
+
123
+ ### `convert()`
124
+
125
+ 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` format) 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.
60
126
 
61
127
  ```ts
62
- import { renderDocToMp4, MemoryContentContainer, readInput } from '@bendyline/squisq-cli/api';
128
+ import { convert, createCliRegistry, ConversionError } from '@bendyline/squisq-cli/api';
63
129
 
64
- // Load a document from disk
65
- const { doc, container } = await readInput('./my-article.md');
130
+ const result = await convert({ kind: 'markdown', markdown: '# Hello' }, 'docx');
131
+ // result.bytes (Uint8Array), result.mimeType, result.suggestedFilename, result.warnings
132
+ ```
133
+
134
+ `ConversionError` (with a stable `code`) and the `ConvertSource` / `ConvertOptions` / `ConversionResult` / `FormatRegistry` types are re-exported for convenience.
135
+
136
+ ### `renderDocToMp4`
137
+
138
+ ```ts
139
+ import { renderDocToMp4, readInput } from '@bendyline/squisq-cli/api';
140
+ import { markdownToDoc } from '@bendyline/squisq/doc';
141
+
142
+ // Load a document from disk (.md, .zip/.dbk, folder, or Doc .json)
143
+ const input = await readInput('./my-article.md');
144
+
145
+ // readInput returns { container, markdownDoc, doc? } — doc is only set for
146
+ // JSON input, so build the Doc from markdown when needed:
147
+ const doc = input.doc ?? markdownToDoc(input.markdownDoc!);
66
148
 
67
149
  // Render to MP4
68
- const result = await renderDocToMp4(doc, container, {
150
+ const result = await renderDocToMp4(doc, input.container, {
69
151
  outputPath: './output.mp4',
70
- fps: 30,
71
- quality: 'high',
72
- orientation: 'landscape',
73
- captionStyle: 'social',
152
+ fps: 30, // default 30
153
+ quality: 'high', // 'draft' | 'normal' | 'high' (default 'normal')
154
+ orientation: 'landscape', // 'landscape' | 'portrait' (default 'landscape')
155
+ captionStyle: 'social', // 'standard' | 'social' — omit for no captions
156
+ coverPreRoll: 2, // seconds of cover-slide pre-roll (default 0)
74
157
  onProgress: (phase, pct) => console.log(`${phase}: ${pct}%`),
75
158
  });
76
159
 
77
- console.log(`Rendered ${result.frameCount} frames (${result.duration}s)`);
160
+ console.log(`Rendered ${result.frameCount} frames (${result.duration}s) → ${result.outputPath}`);
78
161
  ```
79
162
 
163
+ 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.
164
+
80
165
  ### `extractThumbnails`
81
166
 
82
167
  Extract JPEG thumbnails from the first frame of a rendered video:
@@ -87,34 +172,31 @@ import { extractThumbnails } from '@bendyline/squisq-cli/api';
87
172
  await extractThumbnails({
88
173
  videoPath: './output.mp4',
89
174
  outputDir: './thumbs',
90
- slug: 'my-article',
175
+ slug: 'my-article', // filenames: {slug}-{width}x{height}.jpg
91
176
  sizes: [
92
177
  { name: 'og', width: 1200, height: 630, filter: 'scale=1200:630' },
93
178
  { name: 'thumb', width: 480, height: 270, filter: 'scale=480:270' },
94
179
  ],
180
+ force: false, // overwrite existing files (default false)
95
181
  });
96
182
  ```
97
183
 
98
- See the full [API Reference](../../docs/API.md#bendylinesquisq-cli) for all types and options.
99
-
100
- ## Input Formats
184
+ ### Other exports
101
185
 
102
- The CLI accepts three types of input:
186
+ - `readInput(inputPath)` `{ container: MemoryContentContainer, markdownDoc: MarkdownDocument | null, doc?: Doc }`
187
+ - `MemoryContentContainer` (re-export from `@bendyline/squisq/storage`)
188
+ - `VideoQuality`, `VideoOrientation` types (re-exports from `@bendyline/squisq-video`)
103
189
 
104
- | Input | Description |
105
- | ------------- | --------------------------------------------------------- |
106
- | `.md` file | Plain Markdown file |
107
- | `.zip`/`.dbk` | Container archive with document + embedded media |
108
- | Folder | Directory with a Markdown file and associated media files |
190
+ See the full [API Reference](../../docs/API.md#bendylinesquisq-cli) for all types and options.
109
191
 
110
192
  ## Related Packages
111
193
 
112
- | Package | Description |
113
- | ------------------------------------------------------------------------------------ | ----------------------------------------------- |
114
- | [@bendyline/squisq](https://www.npmjs.com/package/@bendyline/squisq) | Headless core — schemas, templates, markdown |
115
- | [@bendyline/squisq-formats](https://www.npmjs.com/package/@bendyline/squisq-formats) | DOCX, PDF, HTML import/export (used by convert) |
116
- | [@bendyline/squisq-video](https://www.npmjs.com/package/@bendyline/squisq-video) | Headless video rendering (used by video) |
117
- | [@bendyline/squisq-react](https://www.npmjs.com/package/@bendyline/squisq-react) | React components for rendering docs |
194
+ | Package | Description |
195
+ | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
196
+ | [@bendyline/squisq](https://www.npmjs.com/package/@bendyline/squisq) | Headless core — schemas, templates, markdown |
197
+ | [@bendyline/squisq-formats](https://www.npmjs.com/package/@bendyline/squisq-formats) | DOCX/PDF/HTML/EPUB/PPTX converters (used by `convert`) |
198
+ | [@bendyline/squisq-video](https://www.npmjs.com/package/@bendyline/squisq-video) | Headless video rendering foundation (used by `video`) |
199
+ | [@bendyline/squisq-react](https://www.npmjs.com/package/@bendyline/squisq-react) | React components + standalone player bundle |
118
200
 
119
201
  ## License
120
202
 
package/dist/api.d.ts CHANGED
@@ -1,39 +1,64 @@
1
1
  import { Doc } from '@bendyline/squisq/schemas';
2
- import { MemoryContentContainer } from '@bendyline/squisq/storage';
2
+ import { ContentContainer } from '@bendyline/squisq/storage';
3
3
  export { MemoryContentContainer } from '@bendyline/squisq/storage';
4
4
  import { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';
5
5
  export { VideoOrientation, VideoQuality } from '@bendyline/squisq-video';
6
+ import { FormatId, FormatRegistry, ConvertSource, ConvertOptions, ConversionResult } from '@bendyline/squisq-formats';
7
+ export { ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionResult, ConvertOptions, ConvertSource, FormatDefinition, FormatId, FormatRegistry, NormalizedInput } from '@bendyline/squisq-formats';
6
8
  import { MarkdownDocument } from '@bendyline/squisq/markdown';
7
9
 
8
10
  /**
9
11
  * readInput
10
12
  *
11
- * Unified input reader for the CLI. Accepts a path to a .md file, .json file
12
- * (Doc schema), .zip/.dbk container, or a folder and returns a MemoryContentContainer
13
- * (virtual file system) plus either the parsed MarkdownDocument or a pre-built Doc.
13
+ * Unified input reader for the CLI. Accepts a path to a markdown file, a JSON
14
+ * Doc file, a `.zip`/`.dbk` container, a folder, or any importable binary
15
+ * format (`.docx`/`.pptx`/`.pdf`/`.xlsx`/`.csv`/`.html`/`.htm`) and always
16
+ * resolves it to a fully-populated {@link ReadInputResult}: a `Doc`, a
17
+ * `ContentContainer` holding the document + any bundled media, the parsed
18
+ * `MarkdownDocument` when the source was markdown-shaped, and the detected
19
+ * `sourceFormat`.
14
20
  *
15
- * When a .json file or a container/folder containing doc.json is provided, the
16
- * Doc is returned directly and markdownDoc will be null. Callers should check
17
- * result.doc first; when present, skip markdownToDoc() and use it directly.
21
+ * Binary formats are dispatched through the shared format registry's importer
22
+ * (preferring `importContainer`, falling back to `importDoc`); every path
23
+ * derives the `Doc` via `markdownToDoc()` when the source is markdown-shaped.
18
24
  */
19
25
 
20
26
  interface ReadInputResult {
21
- container: MemoryContentContainer;
22
- /** Parsed markdown document. Null when input is a Doc JSON file. */
23
- markdownDoc: MarkdownDocument | null;
24
- /** Pre-built Doc from JSON input. Present when input is .json or contains doc.json. */
25
- doc?: Doc;
27
+ /** The resolved document. Always present. */
28
+ doc: Doc;
29
+ /** Virtual file system holding the document plus any bundled media. */
30
+ container: ContentContainer;
31
+ /**
32
+ * The parsed markdown document. Present when the source was markdown-shaped
33
+ * (`.md`, container/folder markdown, or a binary format imported as
34
+ * markdown); absent for JSON-Doc input.
35
+ */
36
+ markdownDoc?: MarkdownDocument;
37
+ /** Detected source format id (registry id, or `md`/`json`/`dbk`/`folder`). */
38
+ sourceFormat: FormatId;
26
39
  }
27
40
  /**
28
- * Read input from a file path (markdown, JSON Doc, ZIP/DBK container, or folder)
29
- * and return a populated ContentContainer + parsed MarkdownDocument or Doc.
30
- *
31
- * When the input is a .json file (or a container/folder containing doc.json),
32
- * the result will have `doc` populated and `markdownDoc` set to null.
33
- * Otherwise, `markdownDoc` is populated and `doc` is undefined.
41
+ * Read input from a file path (markdown, JSON Doc, ZIP/DBK container, folder,
42
+ * or an importable binary format) and resolve it to a populated
43
+ * {@link ReadInputResult}.
34
44
  */
35
45
  declare function readInput(inputPath: string): Promise<ReadInputResult>;
36
46
 
47
+ /**
48
+ * CLI format registry.
49
+ *
50
+ * The CLI extends the formats package's default registry with an `mp4` format
51
+ * so `convert()` can produce video the same way it produces DOCX/PDF/HTML. MP4
52
+ * export is Node-only (it needs Playwright + FFmpeg), which is why it lives
53
+ * here in the CLI rather than in the browser-pure formats package.
54
+ */
55
+
56
+ /**
57
+ * Build the CLI's format registry: every built-in format from
58
+ * `@bendyline/squisq-formats` plus the CLI-only `mp4` exporter.
59
+ */
60
+ declare function createCliRegistry(): FormatRegistry;
61
+
37
62
  /**
38
63
  * Programmatic Video API
39
64
  *
@@ -55,6 +80,24 @@ declare function readInput(inputPath: string): Promise<ReadInputResult>;
55
80
  * });
56
81
  */
57
82
 
83
+ /**
84
+ * Convert a document to a target format using the CLI's format registry.
85
+ *
86
+ * This is a thin, pre-bound wrapper over `convert()` from
87
+ * `@bendyline/squisq-formats`: it injects the CLI registry (which adds the
88
+ * `mp4` format on top of every built-in exporter) and a default
89
+ * `resolvePlayerScript` that lazily loads the standalone player IIFE bundle
90
+ * (required for HTML/EPUB-style exports). Callers may override either via
91
+ * `options`.
92
+ *
93
+ * @param source - A bytes / markdown / doc {@link ConvertSource}.
94
+ * @param to - Target format id (`docx`, `pdf`, `pptx`, `html`, `mp4`, …).
95
+ * @param options - Conversion options; `registry` and `resolvePlayerScript`
96
+ * default to the CLI's values but can be overridden.
97
+ * @returns The encoded bytes plus mime type, suggested filename, and warnings.
98
+ * @throws {@link ConversionError} on any failure, with a stable `code`.
99
+ */
100
+ declare function convert(source: ConvertSource, to: FormatId, options?: ConvertOptions): Promise<ConversionResult>;
58
101
  /** Options for renderDocToMp4. */
59
102
  interface RenderDocToMp4Options {
60
103
  /** Output file path for the MP4. */
@@ -71,7 +114,13 @@ interface RenderDocToMp4Options {
71
114
  height?: number;
72
115
  /** Caption style to bake into the video (default: none). */
73
116
  captionStyle?: 'standard' | 'social';
74
- /** Seconds of cover-slide pre-roll before the story starts (default: 0). */
117
+ /**
118
+ * Seconds of cover-slide pre-roll before the story starts (default: 0).
119
+ *
120
+ * Note: the `squisq video` CLI defaults its `--cover-preroll` flag to 2
121
+ * seconds; this programmatic API deliberately defaults to 0 so library
122
+ * callers get exactly the duration they ask for.
123
+ */
75
124
  coverPreRoll?: number;
76
125
  /**
77
126
  * Progress callback. Called with a phase name and 0-100 percentage.
@@ -96,14 +145,15 @@ interface RenderDocToMp4Result {
96
145
  *
97
146
  * Requires:
98
147
  * - Playwright (chromium) — for headless frame capture
99
- * - FFmpeg — for video encoding (must be on PATH)
148
+ * - FFmpeg — for video encoding (resolved from SQUISQ_FFMPEG, PATH, or an
149
+ * optionally installed `ffmpeg-static` package — see detectFfmpeg)
100
150
  *
101
151
  * @param doc - The Doc structure to render
102
152
  * @param container - MemoryContentContainer with audio/image files
103
153
  * @param options - Rendering and encoding options
104
154
  * @returns Result with duration and frame count
105
155
  */
106
- declare function renderDocToMp4(doc: Doc, container: MemoryContentContainer, options: RenderDocToMp4Options): Promise<RenderDocToMp4Result>;
156
+ declare function renderDocToMp4(doc: Doc, container: ContentContainer, options: RenderDocToMp4Options): Promise<RenderDocToMp4Result>;
107
157
  /** A thumbnail size specification. */
108
158
  interface ThumbnailSpec {
109
159
  /** Label for the thumbnail (used in filename: `{slug}-{width}x{height}.jpg`). */
@@ -134,4 +184,4 @@ interface ExtractThumbnailsOptions {
134
184
  */
135
185
  declare function extractThumbnails(options: ExtractThumbnailsOptions): Promise<void>;
136
186
 
137
- export { type ExtractThumbnailsOptions, type ReadInputResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type ThumbnailSpec, extractThumbnails, readInput, renderDocToMp4 };
187
+ export { type ExtractThumbnailsOptions, type ReadInputResult, type RenderDocToMp4Options, type RenderDocToMp4Result, type ThumbnailSpec, convert, createCliRegistry, extractThumbnails, readInput, renderDocToMp4 };