From f37dc7382900de896be336f57377ad8546a45aa9 Mon Sep 17 00:00:00 2001 From: Magnus Hedemark Date: Thu, 6 Aug 2026 20:11:19 -0400 Subject: [PATCH] =?UTF-8?q?feat(skill):=20add=20anydoc=20=E2=80=94=20offic?= =?UTF-8?q?e=20documents=20to=20GitHub-Flavored=20Markdown=20(#295)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(skill): add anydoc core content and references Add the anydoc skill content tree: SKILL.md (progressive-disclosure index with frontmatter per ALLOWED_FIELDS), human-facing README, the five reference files (formats, cli-reference, errors, workflows, sources), 24 committed fixtures (valid + error cases), and a fixture-grounded eval manifest with 8 cases. Every documented behavior, exit code, and error message was verified against the real pinned CLI (npx -y @firecrawl/anydoc@0.1.6); verbatim --help and error transcripts are reproduced character-for-character. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(skill): add anydoc wrapper script and unit tests Implements scripts/anydoc, a stdlib-only Python wrapper around the pinned @firecrawl/anydoc@0.1.6 CLI: convert/batch/info subcommands, global --json/--dry-run, input and output pre-validation, friendly hints for the no-OCR/encrypted/malformed/unsupported error classes, Node >= 20 and npx availability checks, deterministic batch output naming with documented duplicate/collision behavior, and exit codes 0/1/2. Adds offline unittest suite (46 tests, real-CLI tests skip when npx is unavailable) and keeps the wrapper contract documented in cli-reference.md and errors.md. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(skill): ratchet anydoc evals to 14 grounded cases Verify the pre-authored 8-case manifest and extend it with six high-signal cases (PDF lower-fidelity pipeline, legacy .ppt table flattening, ODP same-serializer, RTF, EPUB, CSV header promotion), each grounded in real pinned-CLI runs against the committed fixtures. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(skill): integrate anydoc into repo catalog and artifacts Add the sorted anydoc catalog entry to README.md (between agent-skills and api-design-and-evolution), regenerate the tracked catalog artifacts (.claude-plugin/marketplace.json, .codex-plugin/plugin.json, .agents/plugins/marketplace.json, llms.txt) with the ruby generators, and add a routing note to documents/SKILL.md pointing office-document to-markdown conversion at the anydoc skill. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix(skill): polish anydoc wrapper timeout, JSON shape, and docs - run_cli raises CliTimeoutError on the 120s timeout; convert/batch with --json now emit one parseable JSON error envelope (error_class "timeout") on stdout before exiting, so --json always yields exactly one JSON doc - batch JSON failure entries (pre-validation and CLI) now carry error_class ("io" for missing/dir inputs, mapped classes for CLI failures), so all batch failure entries share the same shape - build_cli_command places -o/-f before the -- separator for dash-leading filenames, so `convert -f csv -- -weird` converts instead of misparsing ("unexpected second input"); absolute-path inputs unchanged - workflows.md vault-ingestion recipe globs notes/* instead of docs/* and warns to run from a temp/vault dir, never touching repo-root docs/ - unit tests: +6 (timeout envelope x4, batch error_class shape, dash-leading filename); suite grows 46 -> 52 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .claude-plugin/marketplace.json | 9 + .codex-plugin/plugin.json | 1 + README.md | 4 + anydoc/README.md | 61 ++ anydoc/SKILL.md | 185 ++++ anydoc/evals/evals.json | 159 ++++ anydoc/fixtures/book.epub | Bin 0 -> 6167 bytes anydoc/fixtures/empty--errors.docx | 0 anydoc/fixtures/encrypted--errors.odt | Bin 0 -> 658 bytes .../fixtures/fixture-handmade-numbering.docx | Bin 0 -> 2892 bytes anydoc/fixtures/fixture-handmade-outline.docx | Bin 0 -> 1413 bytes anydoc/fixtures/fixture-handmade-quoted.csv | 4 + anydoc/fixtures/fixture-handmade-rich.docx | Bin 0 -> 3268 bytes .../fixtures/fixture-handmade-semicolon.csv | 3 + anydoc/fixtures/fixture-handmade-tables.docx | Bin 0 -> 1348 bytes anydoc/fixtures/fixture-handmade-utf16.csv | Bin 0 -> 62 bytes anydoc/fixtures/fixture-sheet.csv | 8 + anydoc/fixtures/fixture-text.pdf | Bin 0 -> 90060 bytes anydoc/fixtures/handmade-merged.xlsx | Bin 0 -> 1686 bytes anydoc/fixtures/pres.odp | Bin 0 -> 19065 bytes anydoc/fixtures/pres.ppt | Bin 0 -> 464384 bytes anydoc/fixtures/pres.pptx | Bin 0 -> 15734 bytes anydoc/fixtures/scanned-image-only.pdf | Bin 0 -> 2182 bytes anydoc/fixtures/sheet.ods | Bin 0 -> 16142 bytes anydoc/fixtures/sheet.xls | Bin 0 -> 7168 bytes anydoc/fixtures/sheet.xlsx | Bin 0 -> 7106 bytes anydoc/fixtures/text.doc | Bin 0 -> 20480 bytes anydoc/fixtures/text.odt | Bin 0 -> 25489 bytes anydoc/fixtures/text.rtf | 177 ++++ anydoc/fixtures/unsupported.xyz | 1 + anydoc/references/cli-reference.md | 194 +++++ anydoc/references/errors.md | 222 +++++ anydoc/references/formats.md | 402 +++++++++ anydoc/references/sources.md | 97 +++ anydoc/references/workflows.md | 190 ++++ anydoc/scripts/anydoc | 672 +++++++++++++++ anydoc/tests/test_anydoc.py | 810 ++++++++++++++++++ documents/SKILL.md | 4 + llms.txt | 1 + 39 files changed, 3204 insertions(+) create mode 100644 anydoc/README.md create mode 100644 anydoc/SKILL.md create mode 100644 anydoc/evals/evals.json create mode 100644 anydoc/fixtures/book.epub create mode 100644 anydoc/fixtures/empty--errors.docx create mode 100644 anydoc/fixtures/encrypted--errors.odt create mode 100644 anydoc/fixtures/fixture-handmade-numbering.docx create mode 100644 anydoc/fixtures/fixture-handmade-outline.docx create mode 100644 anydoc/fixtures/fixture-handmade-quoted.csv create mode 100644 anydoc/fixtures/fixture-handmade-rich.docx create mode 100644 anydoc/fixtures/fixture-handmade-semicolon.csv create mode 100644 anydoc/fixtures/fixture-handmade-tables.docx create mode 100644 anydoc/fixtures/fixture-handmade-utf16.csv create mode 100644 anydoc/fixtures/fixture-sheet.csv create mode 100644 anydoc/fixtures/fixture-text.pdf create mode 100644 anydoc/fixtures/handmade-merged.xlsx create mode 100644 anydoc/fixtures/pres.odp create mode 100644 anydoc/fixtures/pres.ppt create mode 100644 anydoc/fixtures/pres.pptx create mode 100644 anydoc/fixtures/scanned-image-only.pdf create mode 100644 anydoc/fixtures/sheet.ods create mode 100644 anydoc/fixtures/sheet.xls create mode 100644 anydoc/fixtures/sheet.xlsx create mode 100644 anydoc/fixtures/text.doc create mode 100644 anydoc/fixtures/text.odt create mode 100644 anydoc/fixtures/text.rtf create mode 100644 anydoc/fixtures/unsupported.xyz create mode 100644 anydoc/references/cli-reference.md create mode 100644 anydoc/references/errors.md create mode 100644 anydoc/references/formats.md create mode 100644 anydoc/references/sources.md create mode 100644 anydoc/references/workflows.md create mode 100755 anydoc/scripts/anydoc create mode 100644 anydoc/tests/test_anydoc.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 47a945b..95411b3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -50,6 +50,15 @@ "strict": false, "description": "Use this skill when creating, reviewing, or editing Agent Skills-format skills, or when implementing skill discovery and loading in an agent client. It covers directory structure, SKILL.md metadata, progressive disclosure, evals, and repository conventions. Do not use this skill for general software work that does not involve the Agent Skills format or lifecycle." }, + { + "name": "anydoc", + "source": "./", + "skills": [ + "./anydoc" + ], + "strict": false, + "description": "Convert Word (.doc/.docx/.docm), PowerPoint (.ppt/.pps/.pot/.pptx/.pptm/.ppsx/.ppsm), Excel (.xls/.xlsx/.xlsm/.xlsb), OpenDocument (.odt/.ods/.odp), RTF, EPUB, CSV, and PDF documents to clean GitHub-Flavored Markdown locally with the Any Doc CLI (npx -y @firecrawl/anydoc@0.1.6): headings, GFM tables, slide structure, and footnotes in one pass. Use when a task needs the contents of an office document, spreadsheet, presentation, ebook, or PDF you cannot read directly. Do not use for generating, editing, or validating documents (use documents), for ebook packaging (use epub), or for OCR of scanned or image-only PDFs (anydoc does not OCR; route to OCR tooling)." + }, { "name": "api-design-and-evolution", "source": "./", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 124e53f..32998bc 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -22,6 +22,7 @@ "./agent-council", "./agent-evals-and-observability", "./agent-skills", + "./anydoc", "./api-design-and-evolution", "./artifact-pyramids", "./autogen", diff --git a/README.md b/README.md index 720c9e8..80dcd96 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Operate an evaluated agent with tools and authority in production through a runt Reference for the Agent Skills open format itself — directory structure, frontmatter schema, naming conventions, and progressive disclosure model. Use this meta-skill when creating or reviewing any other skill in this repository. +### [anydoc](anydoc/SKILL.md) + +Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and text-based PDF documents to GitHub-Flavored Markdown locally with the Any Doc CLI. Use when a task needs the contents of an office document, spreadsheet, presentation, ebook, or PDF you cannot read directly. Do not use for generating, editing, or validating documents, for ebook packaging, or for OCR of scanned or image-only PDFs. + ### [api-design-and-evolution](api-design-and-evolution/SKILL.md) Design, document, review, and evolve consumer-facing HTTP, GraphQL, RPC, event, webhook, and streaming interfaces. Covers consumer jobs, domain semantics, contracts, failure behavior, compatibility, deprecation, migration, and deployed-boundary verification. diff --git a/anydoc/README.md b/anydoc/README.md new file mode 100644 index 0000000..9265a07 --- /dev/null +++ b/anydoc/README.md @@ -0,0 +1,61 @@ +# anydoc — office documents to GitHub-Flavored Markdown + +Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF files into clean, LLM-friendly GitHub-Flavored Markdown — entirely on your own machine, with no API keys and no file uploads. One command turns a report, spreadsheet, or slide deck into markdown you (or an agent) can read, summarize, quote, and feed into a knowledge base. + +## Why Install This Skill + +Office documents are opaque to agents. A `.docx` or `.pptx` is a binary zip; a `.xls` is an OLE container; a PDF can be anything. Reading them directly means parsing formats, handling encodings, and reconstructing structure by hand — exactly the work anydoc automates. This skill gives your agent a single, verified command that converts all 8 format families (21 extensions) into GitHub-Flavored Markdown with headings, GFM tables, slide structure, and footnotes preserved, plus the knowledge of exactly where fidelity is lost (Excel number formats, legacy PowerPoint tables, PDF tables). + +The skill wraps the pinned `@firecrawl/anydoc` v0.1.6 CLI with a small helper script that adds input checks, friendly error hints for the known failure classes (scanned PDFs, encrypted files, malformed archives), batch conversion, dry-run planning, and JSON output — so an agent gets predictable exit codes and messages instead of guessing. It also documents the exact error vocabulary of the real CLI, so failures like "PDF has no extractable text ... OCR is required" are recognized and routed correctly (to OCR tooling) rather than retried blindly. + +## What You Get + +| Directory / file | What it provides | +| --- | --- | +| `SKILL.md` + `README.md` | The skill index (trigger, command map, verification steps) and this human-facing guide | +| `scripts/` | `anydoc` — an executable Python 3 wrapper with `convert` (single file or stdin, `-o` output), `batch` (many files, per-file status, summary), and `info` (tool + pinned CLI version), plus global `--json` and `--dry-run` | +| `references/` | Five focused guides: `formats.md` (what GFM each format produces, with fidelity caveats), `cli-reference.md` (verbatim `--help`, every flag, stdout/stderr conventions), `errors.md` (exit codes and the exact error messages), `workflows.md` (recipes: single conversion, batch, vault ingestion, piping, output verification), `sources.md` (upstream URLs, fixture provenance, verification procedure) | +| `tests/` | Unit tests for the wrapper (argparse, pre-validation, hints, dry-run, JSON, batch) — runnable offline | +| `evals/` | An eval manifest with fixture-backed cases covering docx→headings, xlsx→tables, pptx→slide structure, csv→table, legacy `.doc`, ODS preserved values, ODT, and the image-only-PDF OCR failure | +| `fixtures/` | 24 tiny sample documents (all < 5 MB): valid samples for every family plus error cases (image-only PDF, encrypted ODT, empty DOCX, unsupported extension) — used by the tests, evals, and recipes | + +## Quick Start + +You need Node.js 20+ and `npx` (no other install — the CLI and its native binary are fetched on first use): + +```bash +cd anydoc +npx -y @firecrawl/anydoc@0.1.6 fixtures/fixture-handmade-outline.docx +``` + +This converts the sample Word document and prints GitHub-Flavored Markdown to stdout (note the `#`/`##`/`###` heading lines). To write to a file instead: + +```bash +npx -y @firecrawl/anydoc@0.1.6 fixtures/fixture-handmade-outline.docx -o outline.md +``` + +Or use the wrapper for the same job: + +```bash +python3 scripts/anydoc convert fixtures/fixture-handmade-outline.docx -o outline.md +``` + +## Triggers + +Load this skill when the task involves any of these: + +- "Convert this Word/Excel/PowerPoint/PDF/EPUB/CSV file to markdown" +- "Extract the headings, tables, or slide content from this document" +- "Summarize this report / spreadsheet / deck" +- "Turn this CSV into a markdown table" +- "Read this document into markdown for a knowledge base or vault" +- "Convert this PDF to markdown" — but only for text-based PDFs; scanned or image-only PDFs fail (anydoc does not OCR) + +Do **not** load this skill for document generation or editing ("create a docx report", "build a PDF proposal", "validate this document") — that is the `documents` skill's job — or for EPUB authoring (`epub` skill). + +## Requirements + +- **Node.js >= 20** and `npx` (the CLI is distributed via npm; the native binary ships as a platform-specific npm `optionalDependency`, so there is no manual install or compilation). +- **Network once** — the first `npx` run downloads the package and binary; later runs use the npm cache. For permanent or fully offline use, run `npm install -g @firecrawl/anydoc` once. +- **Python 3** (standard library only) if you use the `scripts/anydoc` wrapper. +- **No API keys, no services** — conversion happens locally; files never leave your machine. diff --git a/anydoc/SKILL.md b/anydoc/SKILL.md new file mode 100644 index 0000000..686fa8d --- /dev/null +++ b/anydoc/SKILL.md @@ -0,0 +1,185 @@ +--- +name: anydoc +description: >- + Convert Word (.doc/.docx/.docm), PowerPoint (.ppt/.pps/.pot/.pptx/.pptm/.ppsx/.ppsm), + Excel (.xls/.xlsx/.xlsm/.xlsb), OpenDocument (.odt/.ods/.odp), RTF, EPUB, CSV, and + PDF documents to clean GitHub-Flavored Markdown locally with the Any Doc CLI + (npx -y @firecrawl/anydoc@0.1.6): headings, GFM tables, slide structure, and + footnotes in one pass. Use when a task needs the contents of an office document, + spreadsheet, presentation, ebook, or PDF you cannot read directly. Do not use for + generating, editing, or validating documents (use documents), for ebook packaging + (use epub), or for OCR of scanned or image-only PDFs (anydoc does not OCR; route + to OCR tooling). +license: MIT +compatibility: >- + Node.js >= 20 and npx. The pinned CLI is @firecrawl/anydoc@0.1.6; the native + binary ships via npm optionalDependencies (no install step, no postinstall, no + compilation). Conversion runs entirely on your machine — no services, no API + keys, no uploads. The first npx run downloads the package once (network + required); later runs use the npm cache. +metadata: + skills: anydoc, markdown, conversion, docx, xlsx, pptx, pdf, odt, ods, odp, rtf, epub, csv, office, documents, firecrawl + tags: conversion, markdown, office, documents + source: https://github.com/firecrawl/anydoc +allowed-tools: Bash Read +--- + +# Any Doc — office documents to GitHub-Flavored Markdown + +The `anydoc` skill converts office documents, spreadsheets, presentations, +ebooks, CSV, and text-based PDFs into GitHub-Flavored Markdown using the pinned +Any Doc CLI (`@firecrawl/anydoc` v0.1.6). One shared document model and one GFM +serializer produce the same logical output across formats, and conversion runs +locally in milliseconds — no service, no API key, no file upload. + +## Overview + +Load this skill when a task needs the *contents* of a document the agent cannot +read directly: a Word report to summarize, a spreadsheet to turn into a table, +a slide deck to extract, a CSV to analyze, or an ebook or PDF to quote from. + +The skill ships a small Python helper (`scripts/anydoc`) that wraps the pinned +CLI and adds input pre-validation, friendly error hints, batch conversion, and +`--dry-run`/`--json` output. Every recipe in [references/workflows.md](references/workflows.md) +also shows the raw `npx` invocation, so the skill works with or without the +helper. + +## When to use + +- **Convert a document to markdown** — Word, PowerPoint, Excel, OpenDocument, + RTF, EPUB, CSV, or text-based PDF. +- **Extract structure** — headings, GFM tables, slide titles, speaker notes + (as blockquotes), and footnotes. +- **Feed documents to an LLM** — one-pass conversion to clean markdown for + summarization, extraction, or retrieval ingestion. +- **Batch a folder** — convert a directory of mixed office files for a vault + or knowledge base. +- **Read a document from stdin** — pipe bytes into `anydoc -`. + +## Format coverage (summary) + +anydoc covers **8 format families / 21 extensions** through **12 canonical +parsers**. The canonical formats are `doc, docx, odt, pdf, ppt, pptx, rtf, +epub, xlsx, ods, odp, csv`; extension aliases map through them (`.docm`→docx, +`.xls`→xlsx, `.pptm`→pptx, and so on). + +| Family | Extensions | Expected GFM output | +| --- | --- | --- | +| Word | `.doc` `.docx` `.docm` | `#`–`######` headings, GFM tables, `[^n]` footnotes | +| PowerPoint | `.ppt` `.pps` `.pot` `.pptx` `.pptm` `.ppsx` `.ppsm` | slide titles as plain paragraphs, bullet lists, speaker notes as `>` blockquotes, GFM tables (PPTX/ODP; legacy `.ppt` flattens tables to text lines) | +| Excel | `.xls` `.xlsx` `.xlsm` `.xlsb` | `## ` heading + one GFM table per worksheet; number formats dropped (raw cell values) | +| OpenDocument | `.odt` `.ods` `.odp` | same document/slide shapes as DOCX/PPTX; ODS keeps formatted display values | +| Rich Text Format | `.rtf` | same document shape as DOCX/ODT | +| EPUB | `.epub` | `#` chapter headings, GFM tables, internal anchor links | +| CSV | `.csv` | one GFM table; label-like first row promoted to header; delimiter sniffing; UTF-16 with BOM | +| PDF | `.pdf` | headings + inline emphasis, but a lower-fidelity pipeline: tables flatten to text, footnotes and links degrade. **Scanned or image-only PDFs fail** — anydoc does not OCR | + +See [references/formats.md](references/formats.md) for the full per-format +expectations and fidelity caveats, and [references/errors.md](references/errors.md) +for the exact failure messages (including the no-OCR error). + +## Command Map + +Commands are shown relative to the repository root. `` is any document +path (for example `anydoc/fixtures/fixture-handmade-outline.docx`); `-` reads +the document from stdin. + +| Need | Command | +| --- | --- | +| Convert one file (markdown to stdout) | `anydoc/scripts/anydoc convert ` | +| Convert one file to a markdown file | `anydoc/scripts/anydoc convert -o out.md` | +| Convert many files to a directory | `anydoc/scripts/anydoc batch ... --out-dir out/` | +| Show the tool and pinned CLI version | `anydoc/scripts/anydoc info` | +| Raw pinned CLI, one document | `npx -y @firecrawl/anydoc@0.1.6 [-o out.md]` | +| Raw pinned CLI, read stdin | `cat data.csv \| npx -y @firecrawl/anydoc@0.1.6 - --format csv` | + +Notes: + +- `scripts/anydoc` is an executable Python 3 script (shebang `#!/usr/bin/env + python3`); `python3 anydoc/scripts/anydoc ...` is equivalent when the + executable bit is unavailable. +- The raw `npx -y @firecrawl/anydoc@0.1.6` rows are the ground truth for + conversion behavior; the wrapper delegates to exactly that command. +- Always pin `@0.1.6` for reproducible conversions. `-y` answers npx's + "Ok to proceed?" prompt non-interactively — the CLI itself never prompts. +- Both forms share the same contract: one document per invocation, exit code + `0` success / `1` conversion or IO failure / `2` usage error, diagnostics as + exactly one `anydoc: ` line on stderr, and no prompts. + +## Reference Routing + +Load these on demand — one per topic: + +- [references/formats.md](references/formats.md) — the 8 families / 21 + extensions / 12 parsers, what GFM each format produces, and the fidelity + caveats (xlsx/xls number-format drop vs ODS preserved display values, legacy + `.ppt` table flattening, PDF lower-fidelity pipeline, merged-cell covered + spans, ODP same-serializer). +- [references/cli-reference.md](references/cli-reference.md) — verbatim + `--help`, every flag (`-o`, `-f`, `-h`, `-V`, `--format=x`, `--`), stdin via + `-`, stdout/stderr conventions including EPIPE, version pinning, Node >= 20, + and first-run/offline network behavior. +- [references/errors.md](references/errors.md) — exit codes 0/1/2, the verbatim + error-message vocabulary (io, unsupported, malformed, encrypted, EISDIR, + resource-limit, usage errors), the no-OCR caveat, and troubleshooting recipes. +- [references/workflows.md](references/workflows.md) — single conversion, + batch loops, vault ingestion, stdin/stdout piping, output verification, + large-file/resource-limit behavior, and startup cost. +- [references/sources.md](references/sources.md) — upstream URLs, access dates, + fixture provenance, and how every documented claim was verified against the + real CLI. + +## When not to use + +- **Generating, editing, or validating documents** — anydoc only converts + existing documents *to markdown*; it never creates, edits, or checks + documents. Use the `documents` skill for generation, inspection, and + validation of PDF/Word/Excel/PowerPoint artifacts. +- **Ebook packaging or EPUB authoring** — use the `epub` skill. anydoc reads + EPUBs to markdown but never writes or validates EPUB containers. +- **Scanned or image-only PDFs (OCR)** — anydoc does not perform OCR. Such + PDFs fail as `unsupported` with the OCR message; route the file to OCR + tooling or the hosted Firecrawl Parse API instead of retrying locally. Do + not claim local OCR support. +- **HTML and other web content** — HTML is not a supported input format; use a + web-scraping skill instead. +- **Binary media (images, video, audio)** — embedded images render as alt text + only; anydoc cannot transcribe media content. +- **Layout or rendering work** — output is GitHub-Flavored Markdown only; there + is no pagination, font, or template control. +- **Password-protected files** — encrypted documents fail with + `anydoc: document is encrypted`; there is no password or decryption option. + +## Verification + +Confirm a conversion before reporting it as done: + +1. **Check the exit code.** `0` means the CLI produced markdown. `1` means the + document could not be read or converted — read the single `anydoc: ` + stderr line and match it against [references/errors.md](references/errors.md). + `2` means the command itself was a usage error (bad flag, missing input, + invalid `--format`). +2. **Check the output shape.** The markdown must contain the structural markers + your format actually produces: + - Word / ODT / RTF / text-based PDF: `#`/`##` headings. For PDF, do not + expect GFM tables or `[^1]:` footnote definitions — that pipeline + flattens them. + - Spreadsheets (xlsx/xls/ods) and CSV: `|`-delimited GFM tables. xlsx/xls + show raw cell values (`0.155`, `1234.5`); ODS shows formatted display + values (`15.5%`, `$1,234.50`). + - Presentations (pptx/odp): slide titles as plain paragraphs, `>` + blockquote speaker notes, GFM tables. Legacy `.ppt` flattens tables to + bare text lines. + - EPUB: `#` chapter headings and internal anchor links. +3. **Write large outputs to a file with `-o`.** `-o out.md` keeps stdout silent + and gives a reviewable file instead of streaming the whole document into + context. +4. **Verify tables survived.** If the source had tables and the output has no + `|` rows, consult the format caveats — PDF and legacy `.ppt` flatten tables + by design, not by error. + +**Stop when** the conversion exits 0 and the structural markers match the +source format. Do not re-run or retry on a documented failure mode (encrypted, +malformed, scanned/image-only, unsupported) without changing the input; report +the documented message and route as [references/errors.md](references/errors.md) +instructs. diff --git a/anydoc/evals/evals.json b/anydoc/evals/evals.json new file mode 100644 index 0000000..40de679 --- /dev/null +++ b/anydoc/evals/evals.json @@ -0,0 +1,159 @@ +{ + "schema_version": 1, + "skill_name": "anydoc", + "evals": [ + { + "id": "docx-headings", + "prompt": "Convert this Word document to markdown and extract its headings.", + "expected_output": "The conversion exits 0 with empty stderr and emits GitHub-Flavored Markdown whose structure is expressed as ATX headings, including the lines `## Style heading stays a heading`, `### Direct level overrides the style`, and `# Direct outline without a style`.", + "assertions": [ + "The output contains the heading line `## Style heading stays a heading`", + "The output contains the heading line `### Direct level overrides the style`", + "The output contains the heading line `# Direct outline without a style`" + ], + "files": ["fixtures/fixture-handmade-outline.docx"] + }, + { + "id": "xlsx-table-cell-values", + "prompt": "Convert this spreadsheet to markdown and show me the cell values as a table.", + "expected_output": "The conversion exits 0 and emits a `## Values` heading followed by a GFM table whose cells carry the raw cell values: `Percent | 0.155`, `Currency | 1234.5`, `Thousands | 9876543`, `Date | 2026-03-15` — the number formats are dropped, so the output must NOT contain `15.5%` or `$1,234.50`.", + "assertions": [ + "The output contains a `## Values` heading", + "The output contains the table row `| Percent | 0.155 | fifteen and a half |`", + "The output contains the raw value `1234.5` for the Currency row", + "The output does not contain the formatted values `15.5%` or `$1,234.50`" + ], + "files": ["fixtures/sheet.xlsx"] + }, + { + "id": "pptx-slides-structure", + "prompt": "Convert this PowerPoint deck to markdown, keeping the slide structure.", + "expected_output": "The conversion exits 0 and preserves slide structure: slide titles like `Deck Title Slide` and `Numbers Slide` render as plain paragraphs (not headings), top-level bullets carry an indented nested detail, speaker notes render as blockquotes (`> Speaker note for the intro slide.`), and the slide table renders as a GFM table with the row `| North | 42 |`.", + "assertions": [ + "The output contains `Deck Title Slide` as a plain paragraph, not a markdown heading", + "The output contains the blockquote line `> Speaker note for the intro slide.`", + "The output contains the GFM table row `| North | 42 |`" + ], + "files": ["fixtures/pres.pptx"] + }, + { + "id": "csv-table", + "prompt": "Convert this CSV file to a markdown table.", + "expected_output": "The conversion exits 0 and renders the file as a single GFM table with the first row promoted to the header: `| Kind | Value | Note |` with a separator row and body rows carrying the cell values `15.5%` and `fifteen and a half`.", + "assertions": [ + "The output contains the header row `| Kind | Value | Note |`", + "The output contains a row carrying the cell values `15.5%` and `fifteen and a half`" + ], + "files": ["fixtures/fixture-sheet.csv"] + }, + { + "id": "legacy-doc-converts", + "prompt": "Convert this legacy .doc file to markdown.", + "expected_output": "The conversion exits 0 with empty stderr and emits the shared document serializer's GFM shape: a `# Fixture Document` title, `##` section headings (including `## Lists` and `## Table`), a GFM table with merged cells as empty covered spans, and `[^1]: ...` footnote definitions at the end of the document.", + "assertions": [ + "The output contains the heading `# Fixture Document`", + "The output contains at least three `##` section headings including `## Lists` and `## Table`", + "The output contains a `[^1]:` footnote definition block" + ], + "files": ["fixtures/text.doc"] + }, + { + "id": "image-only-pdf-no-ocr", + "prompt": "Convert this scanned PDF to markdown.", + "expected_output": "The conversion FAILS by design: exit code 1, empty stdout, and exactly one stderr line `anydoc: unsupported input: PDF has no extractable text (Scanned, 1 pages): OCR is required`. anydoc does not perform OCR; the correct response is to route the file to OCR tooling or the hosted Firecrawl Parse API, not to retry locally.", + "assertions": [ + "The conversion exits with code 1 and emits no markdown", + "Stderr contains the verbatim message `anydoc: unsupported input: PDF has no extractable text (Scanned, 1 pages): OCR is required`", + "The response states that OCR is required and routes to OCR tooling or Firecrawl Parse rather than retrying locally" + ], + "files": ["fixtures/scanned-image-only.pdf"] + }, + { + "id": "ods-preserved-values", + "prompt": "Convert this OpenDocument spreadsheet to markdown. I need the formatted display values.", + "expected_output": "The conversion exits 0 and emits a `## Values` heading plus a GFM table whose cells carry the FORMATTED display values — `Percent | 15.5%`, `Currency | $1,234.50`, `Thousands | 9,876,543` — explicitly contrasting with the xlsx/xls number-format drop.", + "assertions": [ + "The output contains the table row `| Percent | 15.5% | fifteen and a half |`", + "The output contains the formatted values `15.5%` and `$1,234.50`", + "The output does not contain the raw values `0.155` or `1234.5`" + ], + "files": ["fixtures/sheet.ods"] + }, + { + "id": "odt-converts", + "prompt": "Convert this ODT document to markdown and show me the structure.", + "expected_output": "The conversion exits 0 with empty stderr and emits the document shape shared with DOCX/DOC/RTF: a `# Fixture Document` title, `##` section headings, a GFM table, and `[^1]:` / `[^2]:` footnote definition lines at the end.", + "assertions": [ + "The output contains the heading `# Fixture Document`", + "The output contains `##` section headings such as `## Lists` and `## Table`", + "The output contains the footnote definition `[^1]: Footnote after an astral character.`" + ], + "files": ["fixtures/text.odt"] + }, + { + "id": "pdf-text-lower-fidelity", + "prompt": "Convert this text-based PDF to markdown. Will the table survive the conversion?", + "expected_output": "The conversion exits 0 with empty stderr and preserves top-level structure via `# Fixture Document` and `##` section headings (`## Lists`, `## Table`, `## Notes and special text`), but the PDF pipeline is lower-fidelity: the table flattens into the plain paragraph `Wide head End Tall B2 C2 B3 C3` with no GFM table, footnote markers degrade to inline superscript glyphs with no `[^1]:` definition block, and links are not emitted as markdown links.", + "assertions": [ + "The output contains `# Fixture Document` and the `##` section headings including `## Table`", + "The table region flattens to the plain paragraph `Wide head End Tall B2 C2 B3 C3` with no GFM table row", + "The output contains no `[^1]:` footnote definition block" + ], + "files": ["fixtures/fixture-text.pdf"] + }, + { + "id": "legacy-ppt-flattens-tables", + "prompt": "Convert this legacy PowerPoint file to markdown and keep the slides' content.", + "expected_output": "The conversion exits 0 and preserves slide text: plain-paragraph titles `Deck Title Slide` and `Numbers Slide` and the blockquote speaker note `> Speaker note for the intro slide.`. The Numbers Slide table flattens to bare text lines (`Region`, `Total`, `North`, `42`) rather than a GFM table, unlike PPTX and ODP.", + "assertions": [ + "The output contains the blockquote line `> Speaker note for the intro slide.`", + "The table content renders as bare text lines including `North` and `42`", + "The output does not contain a GFM table row `| North | 42 |`" + ], + "files": ["fixtures/pres.ppt"] + }, + { + "id": "odp-slides-structure", + "prompt": "Convert this OpenDocument presentation to markdown, preserving the slide structure.", + "expected_output": "The conversion exits 0 and emits the same slide shape as PPTX: `Deck Title Slide` renders as a plain paragraph (not a heading), the speaker note renders as the blockquote `> Speaker note for the intro slide.`, and the slide table renders as a GFM table containing the row `| North | 42 |`.", + "assertions": [ + "The output contains `Deck Title Slide` as a plain paragraph, not a markdown heading", + "The output contains the blockquote line `> Speaker note for the intro slide.`", + "The output contains the GFM table row `| North | 42 |`" + ], + "files": ["fixtures/pres.odp"] + }, + { + "id": "rtf-converts", + "prompt": "Convert this RTF document to markdown and extract the structure.", + "expected_output": "The conversion exits 0 with empty stderr and emits the shared document serializer's shape: `# Fixture Document`, `##` section headings including `## Lists` and `## Table`, a GFM table with merged cells as empty covered spans, and `[^1]:` footnote definitions at the end.", + "assertions": [ + "The output contains the heading `# Fixture Document`", + "The output contains `##` section headings such as `## Lists` and `## Table`", + "The output contains the footnote definition `[^1]: Footnote after an astral character.`" + ], + "files": ["fixtures/text.rtf"] + }, + { + "id": "epub-converts", + "prompt": "Convert this EPUB ebook to markdown, keeping the chapter structure.", + "expected_output": "The conversion exits 0 with empty stderr and emits `# Fixture Book`, `# Chapter One` and `# Chapter Two` headings, a GFM table containing `| Bolts | 12 |`, and internal anchor links that resolve to fragments such as `[Chapter Two](#epub-text-ch002-xhtml-chapter-two)`.", + "assertions": [ + "The output contains the chapter heading `# Chapter One`", + "The output contains the GFM table row `| Bolts | 12 |`", + "The output contains the internal anchor link `[Chapter Two](#epub-text-ch002-xhtml-chapter-two)`" + ], + "files": ["fixtures/book.epub"] + }, + { + "id": "csv-quoted-cells", + "prompt": "Convert this CSV to a markdown table. Some cells contain commas and newlines.", + "expected_output": "The conversion exits 0 and renders the file as a single GFM table with the first row promoted to the header: `| name | desc | qty |` followed by a separator row, with quoted content intact — `| padded | comma, inside | 3 |` keeps the embedded comma and `| plain | multi line | 4 |` keeps the embedded newline.", + "assertions": [ + "The output contains the promoted header row `| name | desc | qty |`", + "The output contains the row `| padded | comma, inside | 3 |` with the embedded comma preserved" + ], + "files": ["fixtures/fixture-handmade-quoted.csv"] + } + ] +} diff --git a/anydoc/fixtures/book.epub b/anydoc/fixtures/book.epub new file mode 100644 index 0000000000000000000000000000000000000000..430296407da0ec7ed1c2f994e42baa6ed60a9cdd GIT binary patch literal 6167 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 DQ(3rL literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/empty--errors.docx b/anydoc/fixtures/empty--errors.docx new file mode 100644 index 0000000..e69de29 diff --git a/anydoc/fixtures/encrypted--errors.odt b/anydoc/fixtures/encrypted--errors.odt new file mode 100644 index 0000000000000000000000000000000000000000..c3361881b6a8a108ff578f4f268d94f4a29fc34f GIT binary patch literal 658 zcmWIWW@Zs#fB?mqxMM~<>Oc+%a{zH}W^QUpWkG6UK|xMta$-qlex80=UW#6RVsU1% zUVcGpUP^v)X>Mv>iC#%+MM(hEFpyTom|cf%JP-%6M1hzah?Dd4NE#@hfnPj;9_K8uw;Um;p-aWsO#zHrk|UbmzkDY3^u7W z@O0iG1A#r?wUzGsa=bcuNP)>|>W+|C;t}VTZTz6T$z1d2Z!1lcK7s0}^F8T%?!En& zR&uSG|5T)3*VS2X%JZX@*Q`=hs5Xh5a`Qv|lO><0Sqg?Egh_Dls21zqICb(|gX^{{ zn=e{?T|1Rcs+L zteTET1ZVwwwOj1Yo&~IbGRpP`cr!AIFyM|$U<`mk129MtF^kZJJ<=e07#JEDeSu7* gC`8wY9$N^Fy+9_`xC`)RWdkW=2Et81`V`n@0LB>AKL7v# literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/fixture-handmade-numbering.docx b/anydoc/fixtures/fixture-handmade-numbering.docx new file mode 100644 index 0000000000000000000000000000000000000000..c437ff83b30c709237dcd1b1db2c6e59b31c6aa4 GIT binary patch literal 2892 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 literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/fixture-handmade-semicolon.csv b/anydoc/fixtures/fixture-handmade-semicolon.csv new file mode 100644 index 0000000..cd407d6 --- /dev/null +++ b/anydoc/fixtures/fixture-handmade-semicolon.csv @@ -0,0 +1,3 @@ +a;b;c +"1,5";"2,5";x +"3,0";y;z diff --git a/anydoc/fixtures/fixture-handmade-tables.docx b/anydoc/fixtures/fixture-handmade-tables.docx new file mode 100644 index 0000000000000000000000000000000000000000..635c76b3f2c919bd25df3c672df0ecc701651431 GIT binary patch literal 1348 zcmWIWW@Zs#U|`??V#S!Qpz5@rK$Z~`1A{P-j&{z^D@n~Oi4UnPNG*=ltH{k+J8`$) zVFQu2@_C0oMXB=fa4cLVyQAkofZT%N9RL zSA3th`0IPy^l8V|ZI+t;=gPFAD;`JIzl+_gA(s>J*Rti*iwXbgkKNh*;p)rN=JEk3 zA!Wbz->>z+5Yk~}VBiMQ@kOaQ#rk?6viH0--ys7Yh6{6pUG8N@v9$y^i?>`6oyzRe zIXlcm{^-NYwK1G zYHWCKM#VyYhlgu&of;Xk@80GvDOEd`=9mBA!{@&rCT_mBr=08SgPQD;>Du$`=lu z2)nLYS6#bj@9`7t>r}(toICp;d~CbBhFz=IqGSPc7tgKB3y=76OcQ$K$}IbxJK6F0 z=Dmfo{Dm*Gj3m_88Z+(bXPT?DdeIJL*?iNqiJOc*&DiO@^w`afHv4*tm(FYOE@)YM zGSm9L(9e)1(J5MNS(go%CcD4V75~V3t&#mYb6O`)uCWTU`wrWQ8}=K$*GrYl_|LPB zO@w=$mrN#8Ipbt)7x_A|vlGuAwR$P;S-D_q_EWw`-)p6!AI^NTJp9k4ua4?ZwR6;~ zZ`WGfjytzI`=4?$tEssAl#SEt^NW2A)HM^o^js3*v;2NWJn2}m-9ko@ilq;%{oS5z z^FDgz(i>*f02lUp5-v2@rh21PiJ40_ARbskKbc(>co<(`__asrrhkklKpi@e7Sn4htZRs zbf>jqBBgn=cTU?p<3;^l=4@Cd~ literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/fixture-sheet.csv b/anydoc/fixtures/fixture-sheet.csv new file mode 100644 index 0000000..ab50c87 --- /dev/null +++ b/anydoc/fixtures/fixture-sheet.csv @@ -0,0 +1,8 @@ +Kind,Value,Note +Percent,15.5%,fifteen and a half +Currency,1234.5,dollars +Thousands,9876543,grouped +Date,2026-03-15,ides of March +Duration,26:30:15,over a day +Tiny,0.0000004,four ten-millionths +Boolean,TRUE,yes diff --git a/anydoc/fixtures/fixture-text.pdf b/anydoc/fixtures/fixture-text.pdf new file mode 100644 index 0000000000000000000000000000000000000000..557e1d2d75ff02713e77852ad8aaa34c667c409b GIT binary patch literal 90060 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 F{{c8e>wo|N literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/handmade-merged.xlsx b/anydoc/fixtures/handmade-merged.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8dd24429dd48488401d51cf340db56ebdb0a606d GIT binary patch literal 1686 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 literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/pres.pptx b/anydoc/fixtures/pres.pptx new file mode 100644 index 0000000000000000000000000000000000000000..de93ada99cdc79bb998ee95e7605a532e72b2e90 GIT binary patch literal 15734 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 m_*p1|VmrGjlq!$7jcafT?(Q@+-asQC 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%0DT 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 CI6d+J literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/text.doc b/anydoc/fixtures/text.doc new file mode 100644 index 0000000000000000000000000000000000000000..7de1e813fe5f5feca45e2d80c86c8a514b704df2 GIT binary patch literal 20480 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 literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/text.odt b/anydoc/fixtures/text.odt new file mode 100644 index 0000000000000000000000000000000000000000..a9fdba542c166e6418c0ebd4e38b513c5e530c9f GIT binary patch literal 25489 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%0DM3CC_LUT)S6 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Ⓢ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 literal 0 HcmV?d00001 diff --git a/anydoc/fixtures/text.rtf b/anydoc/fixtures/text.rtf new file mode 100644 index 0000000..24fc6ca --- /dev/null +++ b/anydoc/fixtures/text.rtf @@ -0,0 +1,177 @@ +{\rtf1\ansi\deff3\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fnil\fprq2\fcharset0 Microsoft YaHei;}{\f6\fnil\fprq2\fcharset0 Arial;}{\f7\fswiss\fprq0\fcharset128 Arial;}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;} +{\stylesheet{\s0\snext0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052 Normal;} +{\s1\snext25\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl0\outlinelevel0\nowidctlpar\hyphpar0\ltrpar\fs36\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052 heading 1;} +{\s2\snext25\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052 heading 2;} +{\s3\snext25\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl2\outlinelevel2\nowidctlpar\hyphpar0\ltrpar\fs26\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052 heading 3;} +{\*\cs15\snext15\loch\b0 NotBold;} +{\*\cs16\snext16\loch\b Bold;} +{\*\cs17\snext17\loch\i Italic;} +{\*\cs18\snext18\loch\strike Struck;} +{\*\cs19\snext19\loch\cf9\ul\ulc0 Hyperlink;} +{\*\cs20\snext20 Footnote Characters;} +{\*\cs21\snext21\loch\super footnote reference;} +{\*\cs22\snext22 Endnote Characters;} +{\*\cs23\snext23\loch\super endnote reference;} +{\s24\sbasedon0\snext25\rtlch\af6\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af5 Heading;} +{\s25\sbasedon0\snext25\loch\sl276\slmult1\sb0\sa140 Body Text;} +{\s26\sbasedon25\snext26\rtlch\af7 \ltrch List;} +{\s27\sbasedon0\snext27\rtlch\af7\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i caption;} +{\s28\sbasedon0\snext28\rtlch\af7 \ltrch\loch\noline Index;} +{\s29\snext29\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\b\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052 StrongPara;} +{\s30\sbasedon0\snext30\loch\nowidctlpar\noline Table Contents;} +{\s31\sbasedon0\snext31\rtlch\afs20 \ltrch\loch\fi-340\li340\lin340\noline\fs20 footnote text;} +{\s32\sbasedon0\snext32\rtlch\afs20 \ltrch\loch\fi-340\li340\lin340\noline\fs20 endnote text;} +{\s33\sbasedon0\snext33 Frame Contents;} +}{\*\listtable{\list\listtemplateid1 +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1} +{\list\listtemplateid2 +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi0\li0} +{\listlevel\levelnfc4\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'01);}{\levelnumbers\'01;}\fi0\li0} +{\listlevel\levelnfc2\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi0\li0} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-360\li1800} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-360\li2160} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-360\li2520} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-360\li2880} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-360\li3240} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-360\li3600}\listid2} +{\list\listtemplateid3 +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u8226 ?;}{\levelnumbers;}\f9\fi0\li0} +{\listlevel\levelnfc23\leveljc0\levelstartat1\levelfollow2{\leveltext \'01\u9702 ?;}{\levelnumbers;}\f9\fi0\li0} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-360\li1440} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-360\li1800} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-360\li2160} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-360\li2520} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-360\li2880} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-360\li3240} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-360\li3600}\listid3} +{\list\listtemplateid4 +{\listlevel\levelnfc1\leveljc0\levelstartat1\levelfollow2{\leveltext \'02\'00.;}{\levelnumbers\'01;}\fi0\li0} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'01.;}{\levelnumbers\'01;}\fi-360\li1080} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'02.;}{\levelnumbers\'01;}\fi-360\li1440} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'03.;}{\levelnumbers\'01;}\fi-360\li1800} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'04.;}{\levelnumbers\'01;}\fi-360\li2160} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'05.;}{\levelnumbers\'01;}\fi-360\li2520} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'06.;}{\levelnumbers\'01;}\fi-360\li2880} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'07.;}{\levelnumbers\'01;}\fi-360\li3240} +{\listlevel\levelnfc0\leveljc0\levelstartat1\levelfollow0{\leveltext \'02\'08.;}{\levelnumbers\'01;}\fi-360\li3600}\listid4} +}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}}{\*\generator LibreOffice/25.2.7.2$Windows_X86_64 LibreOffice_project/5cbfd1ab6520636bb5f7b99185aa69bd7456825d}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709\deftab709\deftab709\deftab709\deftab709 +\hyphauto1\viewscale100\formshade\nobrkwrptbl\paperh16838\paperw11906\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sftnnar\saftnnrlc\sectunlocked1\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\fet2\aftnrstcont\aftnstart1\aftnnrlc +{\*\ftnsep\chftnsep}\pgndec\pard\plain \s1\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl0\outlinelevel0\nowidctlpar\hyphpar0\ltrpar\fs36\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Fixture Document} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Plain paragraph with }{\loch\cs16\loch\b\loch +bold}{\loch +, }{\loch\cs17\loch\i\loch +italic}{\loch +, and }{\loch\cs18\loch\strike\loch +struck}{\loch + runs.} +\par \pard\plain \s29\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\b\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Style-bold paragraph with a }{\loch\cs15\loch\b0\loch +NotBold-styled span}{\loch + inside.} +\par \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Lists} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain 1.\tab}\ilvl0\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +First numbered} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain 2.\tab}\ilvl0\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Second numbered} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain a)\tab}\ilvl1\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Alpha sub one} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain b)\tab}\ilvl1\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Alpha sub two} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain i.\tab}\ilvl2\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Roman sub sub} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain 3.\tab}\ilvl0\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Third numbered} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Interrupting paragraph between lists.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain 4.\tab}\ilvl0\ls2 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Fourth, continuing the count} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain IV.\tab}\ilvl0\ls4 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Roman starting at four} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain V.\tab}\ilvl0\ls4 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Roman five} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \u8226\'95\tab}\ilvl0\ls3 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Bullet one} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \u8226\'95\tab}\ilvl0\ls3 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Bullet two} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \u9702\'3f\tab}\ilvl1\ls3 \fi0\li0\lin0\ql\tx0\fi0\li0\lin0\ltrpar{\loch +Nested bullet} +\par \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Table} +\par \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\cellx6425\cellx9638\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +Wide head}\cell\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +End}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clvmgf\cellx3213\cellx6425\cellx9638\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +Tall}\cell\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +B2}\cell\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +C2}\cell\row\pard \trowd\trql\ltrrow\trpaddft3\trpaddt0\trpaddfl3\trpaddl0\trpaddfb3\trpaddb0\trpaddfr3\trpaddr0\clvmrg\cellx3213\cellx6425\cellx9638\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar\loch +\cell\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +B3}\cell\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\intbl\ql\ltrpar{\loch +C3}\cell\row\pard \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Notes and special text} +\par \ql\fi-340\li340\lin340\noline\ltrpar\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Music clef \uc0 \u55348\uc1 \u56606\'3f appears before this footnote}{{\super \loch \chftn{\*\footnote \chftn\pard\plain \s31\rtlch\afs20 \ltrch\loch\fi-340\li340\lin340\noline\fs20{\loch +\tab Footnote after an astral character.} +}} +}{\loch + reference.} +\par \ql\fi-340\li340\lin340\noline\ltrpar\pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +An endnote follows here}{{\super \loch \chftn{\*\footnote\ftnalt \chftn\pard\plain \s32\rtlch\afs20 \ltrch\loch\fi-340\li340\lin340\noline\fs20{\loch +\tab Endnote body text.} +}} +}{\loch +.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Persian with ZWNJ: }{ +\u1605\'3f\u1740\'3f\u8204\'3f\u1582\'3f\u1608\'3f\u1575\'3f\u1607\'3f\u1605\'3f}{\loch +. Family emoji: \uc0 \u55357\uc1 \u56424\'3f\u8205\'3f\uc0 \u55357\uc1 \u56425\'3f\u8205\'3f\uc0 \u55357\uc1 \u56423\'3f.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Markdown specials: *stars* _under_ [bracket] `tick` #hash 1. dotted | pipe.} +\par \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Links and anchors} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +External link to }{{\field{\*\fldinst HYPERLINK "https://example.com/page" }{\fldrslt {\loch\loch\cf9\ul\ulc0\loch +example}{}}}\loch +.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Relative link to }{{\field{\*\fldinst HYPERLINK "file:///anydoc/tests/fixture-src/sibling.odt" }{\fldrslt {\loch\loch\cf9\ul\ulc0\loch +a sibling file}{}}}\loch +.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +{\*\bkmkstart plainmark}{\*\bkmkend plainmark}This plain paragraph carries a bookmark.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Jump to }{{\field{\*\fldinst HYPERLINK "#plainmark" }{\fldrslt {\loch\loch\cf9\ul\ulc0\loch +the bookmarked paragraph}{}}}\loch +.} +\par \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Objects} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Inline image: }{\loch +{\pict{\*\picprop{\sp{\sn wzDescription}{\sv tiny red dot}}{\sp{\sn wzName}{\sv }}}\picscalex1700\picscaley1700\piccropl0\piccropr0\piccropt0\piccropb0\picw1\pich1\picwgoal10\pichgoal10\pngblip +89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d4944415478da63fccfc0500f000485018084a98c21000000004945 +4e44ae426082} +}{\loch + done.} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Text box: }{ +{\shp{\*\shpinst\shpwr2\shpwrk0\shpbypara\shpbyignore\shptop-1362\shpbottom-228\shpbxcolumn\shpbxignore\shpleft0\shpright2835\shpz1{\sp{\sn shapeType}{\sv 202}}{\sp{\sn dxWrapDistLeft}{\sv 72390}}{\sp{\sn dxWrapDistRight}{\sv 72390}}{\sp{\sn dyWrapDistTop}{\sv 72390}}{\sp{\sn dyWrapDistBottom}{\sv 72390}}{\sp{\sn posrelv}{\sv 2}}{\sp{\sn posv}{\sv 1}}{\sp{\sn posrelh}{\sv 2}}{\sp{\sn posh}{\sv 2}}{\sp{\sn dxTextLeft}{\sv 53975}}{\sp{\sn dyTextTop}{\sv 53975}}{\sp{\sn dxTextRight}{\sv 53975}}{\sp{\sn dyTextBottom}{\sv 53975}}{\sp{\sn lineColor}{\sv 0}}{\sp{\sn lineWidth}{\sv 635}}{\shptxt\s33\ql{\loch +Inside the text box.} +\par \pard}}} +}{\loch + after the box.} +\par \pard\plain \s2\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\ilvl1\outlinelevel1\nowidctlpar\hyphpar0\ltrpar\fs30\b\cf0\f3\lang2057\kerning1\dbch\af8\langfe2052{\listtext\pard\plain \tab}\ls1 \fi0\li0\lin0\ql\fi0\li0\lin0\ltrpar{\loch +Quote and code} +\par \pard\plain \s0\rtlch\af6\afs24\alang1081 \ltrch\lang2057\langfe2052\hich\af3\loch\nowidctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang2057\kerning1\dbch\af8\langfe2052\ql\ltrpar{\loch +Value below one millionth: 0.0000004 should survive.} +\par } \ No newline at end of file diff --git a/anydoc/fixtures/unsupported.xyz b/anydoc/fixtures/unsupported.xyz new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/anydoc/fixtures/unsupported.xyz @@ -0,0 +1 @@ +hello world diff --git a/anydoc/references/cli-reference.md b/anydoc/references/cli-reference.md new file mode 100644 index 0000000..7a76560 --- /dev/null +++ b/anydoc/references/cli-reference.md @@ -0,0 +1,194 @@ +# CLI reference: the Any Doc CLI (pinned @firecrawl/anydoc@0.1.6) + +Everything here was captured by running the pinned CLI on this machine +(`npx -y @firecrawl/anydoc@0.1.6`, version 0.1.6, Node v22). The CLI is a 4.7 KB +Node wrapper (`bin.anydoc = cli.js`) around a native NAPI binding that ships as +an npm `optionalDependency` per platform. + +## Verbatim `--help` output + +``` +anydoc: convert documents to GitHub-Flavored Markdown + +Usage: + anydoc [options] + anydoc - [options] < file + +Converts one document per invocation and writes the Markdown to stdout. +Pass - as the input to read the document from stdin. Never prompts; all +diagnostics go to stderr. + +Options: + -o, --output Write the Markdown to instead of stdout + -f, --format Name the input format instead of detecting it: + doc, docx, odt, pdf, ppt, pptx, rtf, epub, xlsx, ods, odp, csv + (extension aliases like xls, docm, ppsx resolve + to these) + -h, --help Print this help and exit + -V, --version Print the version and exit + +The format is detected from the file content; the file extension is the +fallback for signature-less formats (CSV). stdin has no extension, so CSV +input from stdin needs --format csv. Scanned or image-only PDFs need OCR, +which anydoc does not do, and error as unsupported. + +Exit codes: + 0 success + 1 the document could not be read or converted + 2 usage error: unknown option, missing input, or invalid --format + +Examples: + anydoc report.docx + anydoc slides.pptx -o slides.md + anydoc - --format csv < data.csv + curl -s https://example.com/paper.pdf | anydoc - +``` + +`anydoc --version` prints exactly `0.1.6` (verified; both `--help` and +`--version` exit 0 and write to stdout). + +## Invocation forms + +```text +anydoc [options] # convert a path on disk +anydoc - [options] < file # read the document from stdin +``` + +- `-` as the input reads the document from **stdin**. +- The CLI accepts **exactly one document per invocation** — there is no batch + mode. Passing a second input exits 2: + `anydoc: one document per invocation: unexpected second input ''`. + For multiple documents use a shell loop or `scripts/anydoc batch` + (see [workflows.md](workflows.md)). + +## Flag reference + +| Token | Behavior (verified) | +| --- | --- | +| `` | Input path. Format detected from content; extension is the fallback for signature-less formats (CSV). | +| `-` | Read the document from stdin. If stdin is a TTY, exits 2 with `anydoc: stdin is a terminal; pipe or redirect a document into anydoc -`. | +| `-o `, `--output ` | Write the Markdown to `` instead of stdout. **Silently overwrites** an existing file (verified). Writing to a directory fails with exit 1: `anydoc: EISDIR: illegal operation on a directory, open ''`. With `-o`, stdout stays silent. | +| `-f `, `--format ` | Force the input format instead of detecting it. Values: `doc, docx, odt, pdf, ppt, pptx, rtf, epub, xlsx, ods, odp, csv`. Extension aliases resolve through the parser mapping (verified: `--format xls`, `--format docm` accepted). Invalid value → exit 2: `anydoc: invalid format 'bogus'; expected one of: doc, docx, odt, pdf, ppt, pptx, rtf, epub, xlsx, ods, odp, csv`. | +| `-h`, `--help` | Print help to stdout, exit 0. Works even when the native binding is unavailable. | +| `-V`, `--version` | Print the version (`0.1.6`) to stdout, exit 0. Binding-independent like `--help`. | +| `--format=x` | Inline `=` value syntax is supported for long options (verified: `--format=rtf` works). | +| `--` | End of options: everything after `--` is treated as a positional input (a filename starting with `-`). | +| Missing option value | `anydoc: ` markers. + +## Word (`.doc`, `.docx`, `.docm`) + +Expected output: `#` title, `##`/`###` section headings, inline emphasis, +GFM tables, `[^n]` footnotes. DOCX, DOC, ODT, and RTF all share this document +shape; the same fixture converted as `.doc`, `.odt`, and `.rtf` produced +near-identical markdown. + +Real conversion of `fixtures/fixture-handmade-outline.docx`: + +```markdown +## Style heading stays a heading + +### Direct level overrides the style + +Direct nine turns the style heading off + +# Direct outline without a style + +Child style nine stops inheritance +``` + +Headings come from Word styles and direct formatting; `#`–`######` levels map +onto heading levels. Real conversion of `fixtures/text.doc` shows the full +document shape: + +```markdown +# Fixture Document + +Plain paragraph with **bold**, *italic*, and ~~struck~~ runs. + +## Table + +| | | | +| --- | --- | --- | +| Wide head | | End | +| Tall | B2 | C2 | +| | B3 | C3 | + +## Notes and special text + +Music clef 𝄞 appears before this footnote[^1] reference. + +[^1]: Footnote after an astral character. +``` + +Caveats: + +- **Merged cells** in Word tables render as empty covered spans (the covered + cells are blank, not repeated or filled). +- **Nested tables** flatten into a single cell (GFM cannot nest tables) — a + known limitation of the library. +- Legacy `.doc` (OLE) converts through the same document serializer with the + same shape; only the relative-link target rendering differs cosmetically + between sources. +- Fillable-form controls (DOCX content controls) lose their field layer; + labels and underline glyphs survive. + +## PowerPoint (`.ppt`, `.pps`, `.pot`, `.pptx`, `.pptm`, `.ppsx`, `.ppsm`) + +Expected output: **slide titles as plain paragraphs** (never markdown +headings), bullet lists, speaker notes as `>` blockquotes, and — for PPTX and +ODP — slide tables as proper GFM tables. Legacy `.ppt` flattens tables to bare +text lines (see caveat). + +Real conversion of `fixtures/pres.pptx`: + +```markdown +Deck Title Slide + +- Top level point + + - Nested detail + +- Second point with emphasis + +> Speaker note for the intro slide. + +Numbers Slide + +| Region | Total | +| --- | --- | +| North | 42 | + +Grouped shapes below. +``` + +Caveat — **legacy `.ppt` flattens tables to bare text lines.** The same deck +converted from `fixtures/pres.ppt` renders the Numbers Slide table as plain +lines with no `|` table syntax: + +```markdown +Numbers Slide + +Region + +Total + +North + +42 +``` + +If the presentation's tables matter, use PPTX or ODP and verify the `|` rows +survived (see [workflows.md](workflows.md), "Output verification"). + +## Excel (`.xls`, `.xlsx`, `.xlsm`, `.xlsb`) + +Expected output: each worksheet becomes a `## ` heading followed by +a GFM table; the first row is used as the table header when it looks +label-like. + +Real conversion of `fixtures/sheet.xlsx` (first table): + +```markdown +## Values + +| Kind | Value | Note | +| --- | --- | --- | +| Percent | 0.155 | fifteen and a half | +| Currency | 1234.5 | dollars | +| Thousands | 9876543 | grouped | +| Date | 2026-03-15 | ides of March | +| Duration | 26:30:15 | over a day | +| Tiny | 0.0000004 | four ten-millionths | +| Boolean | TRUE | yes | +``` + +Caveats: + +- **XLS/XLSX drop number formats (issue #27).** Cells carry their *raw* + values, not the formatted display values: `Percent → 0.155` (not `15.5%`), + `Currency → 1234.5` (not `$1,234.50`), thousands `9876543`. A percentage + reading as a raw fraction is wrong by 100x in meaning — warn consumers and + sanity-check spreadsheets. Dates survive as ISO strings (`2026-03-15`). +- **ODS is the contrast case:** it keeps the formatted display values + (`15.5%`, `$1,234.50`, `9,876,543`) on the same logical content. If display + values matter, prefer ODS or a CSV export. +- **Merged cells render as empty covered spans** within the populated range + only. Real conversion of `fixtures/handmade-merged.xlsx`: + + ```markdown + | | | | + | --- | --- | --- | + | Merged across | | padded | + | tall | b2 | 3.5 | + | | b3 | | + ``` + +- Hidden rows and columns are treated as visible and appear in the output + (known limitation) — check for hidden template or calculation content before + feeding output to an LLM. + +## OpenDocument (`.odt`, `.ods`, `.odp`) + +- `.odt`: same document shape as DOCX/DOC/RTF — `#`/`##` headings, GFM + tables, `[^n]` footnotes. Real conversion of `fixtures/text.odt` matches the + `text.doc` output structure line-for-line (only relative-link targets differ + in depth). +- `.ods`: same spreadsheet shape as XLSX (`## Values` + GFM table) but with + **formatted display values preserved** — the Excel number-format caveat does + not apply. Real conversion of `fixtures/sheet.ods`: + + ```markdown + ## Values + + | Kind | Value | Note | + | --- | --- | --- | + | Percent | 15.5% | fifteen and a half | + | Currency | $1,234.50 | dollars | + | Thousands | 9,876,543 | grouped | + ``` + +- `.odp`: **same slide serializer as PPTX** — slide titles as plain + paragraphs, speaker notes as blockquotes, and GFM tables **kept** (unlike + legacy `.ppt`). Real conversion of `fixtures/pres.odp`: + + ```markdown + Deck Title Slide + + - Top level point + + - - Nested detail + - Second point with emphasis + + > Speaker note for the intro slide. + + Numbers Slide + + | Region | Total | + | --- | --- | + | North | 42 | + ``` + + One cosmetic difference vs PPTX: a nested bullet renders as `- - Nested + detail` on one line rather than as an indented sub-list. The table, + blockquote notes, and paragraph titles are identical in shape to PPTX. + +## Rich Text Format (`.rtf`) + +Expected output: the same document shape as DOCX/ODT (`# Fixture Document`, +`##` sections, GFM tables, `[^n]` footnote definitions). Real conversion of +`fixtures/text.rtf` matches `text.odt` structure; the one notable difference is +that relative link targets render with a `file:///` absolute path, e.g. +`[a sibling file](file:///anydoc/tests/fixture-src/sibling.odt)`, instead of a +relative path — a known cosmetic quirk. + +## EPUB (`.epub`) + +Expected output: `#` chapter headings (plus the book metadata title), GFM +tables, preserved inline emphasis/code, and **internal anchor links resolved to +fragments**. Real conversion of `fixtures/book.epub`: + +```markdown +# Fixture Book + +# Fixture Book + +anydoc tests + + + +# Chapter One + +Opening paragraph with **bold**, *italic*, and `code` runs. + +See [Chapter Two](#epub-text-ch002-xhtml-chapter-two) for the table, or jump straight to [the marked paragraph](#epub-text-ch002-xhtml-markpoint). + + + +# Chapter Two + +| Name | Qty | +| --- | --- | +| Bolts | 12 | +| Nuts | 30 | +``` + +Notes: the book title may appear twice (metadata title + injected title); +internal links keep working as `[text](#fragment)` links; external links stay +as normal markdown links. + +## CSV (`.csv`) + +Expected output: the file renders as **one GFM table**. The first row is +**promoted to the header row** when it looks like labels (≥ 2 columns, +non-empty, non-numeric, distinct fields) — this behavior ships in 0.1.6. +Quoted fields with embedded commas and newlines are preserved. + +Real conversion of `fixtures/fixture-handmade-quoted.csv`: + +```markdown +| name | desc | qty | +| --- | --- | --- | +| padded | comma, inside | 3 | +| plain | multi line | 4 | +``` + +Also verified: + +- **Delimiter sniffing** — a semicolon-delimited file with decimal commas + splits on `;` and keeps `1,5` intact (real output of + `fixtures/fixture-handmade-semicolon.csv`): + + ```markdown + | a | b | c | + | --- | --- | --- | + | 1,5 | 2,5 | x | + | 3,0 | y | z | + ``` + +- **UTF-16 (with BOM)** decodes to correct Unicode (real output of + `fixtures/fixture-handmade-utf16.csv`): + + ```markdown + | col1 | col2 | + | --- | --- | + | naïve | café | + | Αθήνα | 数据 | + ``` + +CSV has no content signature, so **`--format csv` is required when reading CSV +from stdin** (see [cli-reference.md](cli-reference.md)). + +## PDF (`.pdf`) — the lower-fidelity pipeline + +Text-based PDFs convert **locally** through a separate pipeline (`pdf-inspector`) +that emits Markdown directly — PDF has no document model, so only Markdown +output exists. Real conversion of `fixtures/fixture-text.pdf`: + +```markdown +# Fixture Document + +Plain paragraph with **bold**, *italic*, and struck runs. **Style-bold paragraph with a** NotBold-styled span **inside.** + +## Lists + +1.First numbered +2.Second numbered +a)Alpha sub one +b)Alpha sub two +i.Roman sub sub +3.Third numbered Interrupting paragraph between lists. + +## Table + +Wide head End Tall B2 C2 B3 C3 +``` + +**Fidelity caveats (verified on the real output):** + +- **No GFM tables.** Table cell text flattens into a plain paragraph run + (`Wide head End Tall B2 C2 B3 C3`) — there is no `|` table. +- **No `[^n]` footnotes.** Footnote markers degrade to inline superscript + glyphs (`¹`) and the note bodies drop into the flow; there is no `[^1]:` + definition block. +- **Links are not emitted as markdown links.** They degrade to `underlined + text`. +- Numbered/bulleted list structure compresses (markers inline), and some + Unicode degrades (e.g. emoji without ZWJ). + +### Scanned or image-only PDFs — no OCR + +A PDF with **no extractable text layer** fails as `unsupported` with this exact +message (exit code 1): + +``` +anydoc: unsupported input: PDF has no extractable text (Scanned, 1 pages): OCR is required +``` + +anydoc **does not perform OCR** — the library's stance is explicit, and there +is no password, retry, or OCR option. When this message fires: report the exact +error, state that OCR is required, and route the file to OCR tooling or the +hosted Firecrawl Parse API. Do not retry the same file locally and do not claim +anydoc can OCR it. See [errors.md](errors.md) for the full routing guidance. + +## Formats anydoc does NOT support + +- HTML/SingleFile (open feature request only) — not an input format. +- Images (`.png`, `.jpg`, ...) — no image-to-text conversion. +- Password-protected/encrypted documents — fail with + `anydoc: document is encrypted` (see [errors.md](errors.md)). +- Anything without a recognized signature and extension — fails as + `unsupported input: unrecognized file content and extension: `. + +## Output-shape invariants to remember + +1. One serializer: the same logical structure yields near-identical Markdown + across docx/odt/rtf — do not re-test each office format for the same + feature. +2. Spreadsheets: expect `## ` + GFM tables; warn that xlsx/xls + drop number formats (issue #27) while ODS keeps display values. +3. Legacy `.ppt` and all PDFs lose tabular structure — add a + "verify the table survived" step or use PPTX/ODP and text PDFs. +4. Images never survive as bytes in Markdown — only alt text. diff --git a/anydoc/references/sources.md b/anydoc/references/sources.md new file mode 100644 index 0000000..65fab5d --- /dev/null +++ b/anydoc/references/sources.md @@ -0,0 +1,97 @@ +# Sources, provenance, and verification + +## Upstream project + +| Resource | URL / identifier | +| --- | --- | +| Repository | https://github.com/firecrawl/anydoc | +| npm package | `@firecrawl/anydoc` — https://www.npmjs.com/package/@firecrawl/anydoc | +| PyPI package | `firecrawl-anydoc` (imports as `anydoc`) — https://pypi.org/project/firecrawl-anydoc/ | +| crates.io crate | `anydoc` (same release train) | +| Browser demo (WASM) | https://firecrawl.github.io/anydoc/ | +| License | MIT | + +## Access and verification dates + +- Research and empirical verification performed **2026-08-05** and **2026-08-06** + on macOS (arm64) with Node v22.22.3, network access, and the pinned CLI + `npx -y @firecrawl/anydoc@0.1.6`. +- The pinned release **0.1.6** was published to npm at + **2026-08-05T18:29:40Z**; PyPI wheels for the same version were uploaded + **2026-08-05T18:29Z**. First release was 0.1.1 (2026-08-04). + +## Fixture provenance + +The committed fixtures under `fixtures/` come from two sources, both documented +here per the repository's attribution policy: + +1. **The MIT-licensed upstream test suite.** Most fixtures were downloaded from + `https://github.com/firecrawl/anydoc/tree/main/tests/fixtures` (raw files + via `https://raw.githubusercontent.com/firecrawl/anydoc/main/tests/fixtures/...`). + They retain the upstream naming and structure: + - CSV: `fixture-handmade-quoted.csv`, `fixture-handmade-semicolon.csv`, + `fixture-handmade-utf16.csv`, `fixture-sheet.csv` + - DOCX: `fixture-handmade-numbering.docx`, `fixture-handmade-outline.docx`, + `fixture-handmade-rich.docx`, `fixture-handmade-tables.docx` + - Word legacy: `text.doc`; OpenDocument: `text.odt`, `sheet.ods`, `pres.odp` + - RTF: `text.rtf`; EPUB: `book.epub` + - PowerPoint: `pres.ppt`, `pres.pptx`; Excel: `sheet.xls`, `sheet.xlsx`, + `handmade-merged.xlsx` + - PDF: `fixture-text.pdf` + - Error cases from the upstream `*--errors.*` corpus: + `empty--errors.docx`, `encrypted--errors.odt` +2. **Generated samples** (created during research for cases the upstream suite + does not cover; deterministic, reproducible): + - `scanned-image-only.pdf` — a PDF with a single grayscale image and no + text layer, generated with Pillow, to exercise the no-OCR error path. + - `unsupported.xyz` — a small text file with an unsupported extension, to + exercise the unrecognized-content error path. + +All fixtures are tiny (largest: `pres.ppt` at ~454 KB) and each is well under +the 5 MB repository limit. All committed copies are byte-identical to the +staged originals used during research (verified by sha256). + +MIT license notice: the upstream anydoc project is MIT-licensed (Copyright +Firecrawl); the fixture files above are used under that license. The generated +samples carry no upstream copyright. + +## Verification procedure + +Every factual claim in this skill was confirmed against the **real pinned CLI** +(v0.1.6), not inferred from documentation: + +1. **Environment warm-up**: `node --version` (v22.22.3 ≥ 20), then + `npx -y @firecrawl/anydoc@0.1.6 --version` → prints `0.1.6`; `--help` → + the verbatim help block reproduced in + [cli-reference.md](cli-reference.md). +2. **Positive conversions**: the pinned CLI was run on every committed fixture + with stdout and stderr captured separately and the exit code recorded. All + 20 positive fixtures converted with exit 0 and empty stderr; the captured + markdown was compared against the output expectations documented in + [formats.md](formats.md) (headings, table rows, slide structure, footnote + definitions, CSV header promotion, UTF-16/delimiter handling, merged-cell + covered spans). +3. **Error paths**: each error fixture and each usage error was run with + stderr captured verbatim and the exit code recorded (1 for conversion/IO + failures, 2 for usage errors). The exact messages appear in + [errors.md](errors.md) character-for-character, including + `anydoc: unsupported input: PDF has no extractable text (Scanned, 1 pages): OCR is required`. +4. **Special behaviors**: `-o` overwrite and EISDIR, stdin via `-` with and + without `--format csv`, `--format=x` inline syntax, `--` end-of-options, + extension aliases (`--format xls`, `--format docm`), EPIPE (`| head` exits + 0 with empty stderr), the stdin-is-a-terminal usage error (via a + pseudo-TTY), and resource limits (run on the upstream `zipbomb`/`imagebomb` + fixtures and on a generated 250 MB-entry archive — all exit 1 with the + documented `max_entry_bytes` prefix). +5. **First-run/offline**: a fresh empty npm cache was used to verify the + first-run download path (`env npm_config_cache=$(mktemp -d) npx -y + @firecrawl/anydoc@0.1.6 --version` → `0.1.6`, exit 0). +6. **Startup timing**: repeated warm invocations were timed + (`/usr/bin/time -p npx -y @firecrawl/anydoc@0.1.6 ...`) — ~0.32–0.35 s each, + consistent with the documented ~0.33–0.55 s warm-cache startup range. + +Repository checks applied after authoring: frontmatter and structure +(`ruby scripts/validate-skills.rb`), skill quality (`ruby +scripts/validate-skill-quality.rb --base origin/main`), reference caps and link +resolution, eval-manifest validation (`scripts/validate-evals.py`), and no +machine-specific paths or credentials in any committed file. diff --git a/anydoc/references/workflows.md b/anydoc/references/workflows.md new file mode 100644 index 0000000..9f0d97a --- /dev/null +++ b/anydoc/references/workflows.md @@ -0,0 +1,190 @@ +# Workflows: recipes for converting documents to markdown + +All recipes use the pinned CLI `npx -y @firecrawl/anydoc@0.1.6` (ground truth) +and the skill's wrapper `scripts/anydoc` where it adds value. Commands are +shown relative to the repository root; `anydoc/fixtures/...` paths can be +replaced with any document path. The vault-ingestion recipe (section 5) is +written to be run from a temp or vault directory holding *your own* +documents. Each raw-CLI invocation converts **exactly one document** — there +is no batch mode. + +## 1. Single conversion + +```bash +# Markdown to stdout +npx -y @firecrawl/anydoc@0.1.6 anydoc/fixtures/fixture-handmade-outline.docx + +# Markdown to a file (stdout stays silent; existing file is overwritten) +npx -y @firecrawl/anydoc@0.1.6 anydoc/fixtures/fixture-handmade-outline.docx -o outline.md + +# Same jobs through the wrapper +python3 anydoc/scripts/anydoc convert anydoc/fixtures/fixture-handmade-outline.docx +python3 anydoc/scripts/anydoc convert anydoc/fixtures/fixture-handmade-outline.docx -o outline.md +``` + +Expected result: exit code 0, empty stderr, and GitHub-Flavored Markdown on +stdout (or written to the `-o` output file) containing `#`/`##`/`###` heading +lines. + +## 2. Force the input format + +```bash +# Extensionless or mislabeled file: name the format explicitly +npx -y @firecrawl/anydoc@0.1.6 ./data --format csv +npx -y @firecrawl/anydoc@0.1.6 ./report --format docx +``` + +Use `--format ` only when detection cannot work (CSV from stdin, or a +missing/wrong extension). Aliases resolve: `--format xls`, `--format docm`, +`--format ppsx` are accepted. An invalid name exits 2 with +`anydoc: invalid format 'bogus'; expected one of: ...`. + +## 3. Read a document from stdin + +```bash +# CSV from stdin requires --format csv (no signature, no extension) +printf 'name,role\nAlice,Engineer\n' | npx -y @firecrawl/anydoc@0.1.6 - --format csv + +# Any document type can come from stdin; detection reads the bytes +curl -s https://example.com/paper.pdf | npx -y @firecrawl/anydoc@0.1.6 - +``` + +The wrapper supports the same: `cat data.csv | python3 anydoc/scripts/anydoc convert - -f csv`. + +Piping notes: + +- Markdown goes to **stdout only**; diagnostics are the single + `anydoc: ` stderr line. +- **EPIPE is handled**: if the downstream pipe closes early + (`... anydoc@0.1.6 big.xlsx | head -n 1`), the CLI exits 0 with no stderr + noise — piping into `head` is safe and is not a failure. + +## 4. Batch conversion (raw CLI) + +The raw CLI takes one document per invocation, so batch with a shell loop: + +```bash +mkdir -p out +for f in anydoc/fixtures/*.docx; do + npx -y @firecrawl/anydoc@0.1.6 "$f" -o "out/$(basename "${f%.docx}").md" +done +``` + +Each failed document (error fixtures, scanned PDFs, encrypted files) exits 1 +with its `anydoc: ` on stderr and produces no output file; the loop +continues with the next input. Handle or route those per +[errors.md](errors.md). + +Or the wrapper, which is built for this (per-file status, continues past +failures, summary, and a non-zero exit when any input failed): + +```bash +python3 anydoc/scripts/anydoc batch \ + anydoc/fixtures/fixture-handmade-outline.docx \ + anydoc/fixtures/fixture-sheet.csv \ + --out-dir out/ +``` + +`batch --dry-run --json` prints the plan (input → output, dry-run marker) +without converting or creating anything: + +```bash +python3 anydoc/scripts/anydoc batch anydoc/fixtures/fixture-handmade-outline.docx \ + anydoc/fixtures/fixture-sheet.csv --out-dir out/ --dry-run --json +``` + +## 5. Vault-ingestion pattern + +Convert a folder of mixed office documents to markdown for ingestion into a +vault or knowledge base: + +1. **Collect** the documents into a folder (mixed docx/xlsx/pptx/csv/odt/pdf + is fine — text-based PDFs only; see the no-OCR caveat in + [errors.md](errors.md)). +2. **Batch-convert** with the wrapper into a markdown folder: + + ```bash + python3 anydoc/scripts/anydoc batch notes/*.docx notes/*.xlsx notes/*.csv --out-dir vault/inbox/ + ``` + + (or the raw-CLI loop above if you are not using the wrapper). + + > **Run this from a temp or vault directory — never from the agent-skills + > repo root.** The glob matches whatever directory you name, and the + > repository tracks a top-level `docs/` directory (distinct from the + > `documents/` skill): globbing `docs/*.docx` there, or deleting/cleaning + > those matches, would damage tracked repository files. Keep the source + > documents in their own folder (here `notes/`) and convert into a + > separate `vault/inbox/` folder. +3. **Verify each output** (step 6) — at minimum confirm exit 0 and that the + structural markers your formats produce are present (headings for Word/PDF, + `|` tables for spreadsheets/CSV). +4. **Failures are per-file**: the batch summary names what failed; route those + files per [errors.md](errors.md) (scanned PDF → OCR tooling, encrypted → + unencrypted copy, unsupported → check extension) and re-run only the + failures. + +## 6. Output verification + +Before treating a conversion as done: + +1. **Exit code 0** — the CLI produced markdown. Exit 1: read the + `anydoc: ` stderr line and match it against + [errors.md](errors.md). Exit 2: fix the command (usage error). +2. **Structural markers** — check the markers your format actually produces: + - Word / ODT / RTF / text-based PDF: `#`/`##` heading lines + (`grep -E '^#{1,6} ' out.md`). + - Spreadsheets (xlsx/xls/ods) and CSV: `## ` headings and + `|`-delimited rows (`grep -E '^\|' out.md`). + - Presentations (pptx/odp): slide titles as plain paragraphs, `>` + blockquote speaker notes, `|` table rows (legacy `.ppt` has no `|` rows — + that is by design, not an error). + - EPUB: `#` chapter headings and `[text](#fragment)` internal links. +3. **Tables survived?** If the source had tables and the output has no `|` + rows, check the caveats: PDF and legacy `.ppt` flatten tables by design. +4. **Large outputs**: convert with `-o out.md` and inspect the file rather + than streaming everything into context. + +Use the committed fixtures to sanity-check an environment once: + +```bash +npx -y @firecrawl/anydoc@0.1.6 anydoc/fixtures/fixture-handmade-outline.docx # headings +npx -y @firecrawl/anydoc@0.1.6 anydoc/fixtures/sheet.xlsx # ## Values + table +npx -y @firecrawl/anydoc@0.1.6 anydoc/fixtures/fixture-text.pdf # headings, no table +``` + +## 7. Large files and resource limits + +- **Conversion is not streaming** — the document is read and processed as a + whole, and safety limits protect against decompression and nesting bombs. +- **Zip/image bombs are rejected via `max_entry_bytes`** with exit 1 and the + prefix `anydoc: resource limit exceeded (max_entry_bytes):` (full examples + in [errors.md](errors.md)). This is by design — do not try to bypass it. +- **`-o out.md` is recommended for large documents** so the output is written + to a reviewable file instead of filling stdout/context; you can then read + the parts you need. +- Genuinely large real documents (as opposed to bombs) convert normally; the + per-document limit only rejects entries whose declared decompressed size + exceeds the cap. +- If a resource-limit error fires on a *real* file, the archive is malformed + or hostile — re-export the document rather than disabling the limit. + +## 8. Startup cost and performance + +Each `npx -y @firecrawl/anydoc@0.1.6` invocation costs roughly **0.33–0.55 s +of warm-cache startup** (npm/npx process startup) on top of the conversion +itself, which is a few milliseconds (measured ~5 ms for a PDF, <1 ms for a +DOCX once the process is warm). There is no progress output; conversions are +effectively instant. Plan for ~0.5 s per document in batch loops, and prefer a +single `npx` process per document (you cannot batch inside one invocation). + +## 9. Offline / cold-cache behavior + +- The first `npx` run downloads the package plus the native binary (network + required once); later runs use the npm cache. A cold-cache offline run fails + with a clear npx fetch error before anydoc executes. +- For permanent or fully offline use, install once: + `npm install -g @firecrawl/anydoc`, then call `anydoc ` directly. +- The wrapper always invokes npx with `-y` (non-interactive), so it never + hangs on npx's install prompt — even on a cold cache it fails fast if the + package cannot be fetched. diff --git a/anydoc/scripts/anydoc b/anydoc/scripts/anydoc new file mode 100755 index 0000000..d8d04b1 --- /dev/null +++ b/anydoc/scripts/anydoc @@ -0,0 +1,672 @@ +#!/usr/bin/env python3 +"""anydoc — convert office documents to GitHub-Flavored Markdown locally. + +Python 3 standard-library wrapper around the pinned Any Doc CLI +(`npx -y @firecrawl/anydoc@0.1.6`). Adds input pre-validation, friendly +hints for the known failure classes, batch conversion, and `--json` / +`--dry-run` output on top of the raw CLI. + +Commands: + convert [-o out.md] [-f ] [--json] [--dry-run] + batch [--out-dir DIR] [--json] [--dry-run] + info [--version] + +Exit codes: 0 success / 1 conversion or pre-validation failure / 2 usage error. +With `--json`, exactly one JSON document goes to stdout; all diagnostics go to +stderr. The wrapper never prompts: npx is always invoked with `-y`. +""" + +import argparse +import json +import os +import re +import shutil +import signal +import subprocess +import sys +from pathlib import Path + +TOOL_NAME = "anydoc" +CLI_PACKAGE = "@firecrawl/anydoc" +CLI_VERSION = "0.1.6" +PINNED = "%s@%s" % (CLI_PACKAGE, CLI_VERSION) +NODE_MIN_MAJOR = 20 +RUN_TIMEOUT = 120 + +# The 12 canonical parsers plus the 9 extension aliases the CLI accepts for -f. +CANONICAL_FORMATS = "doc docx odt pdf ppt pptx rtf epub xlsx ods odp csv".split() +FORMAT_ALIASES = "docm xls xlsm xlsb pps pot pptm ppsx ppsm".split() +VALID_FORMATS = frozenset(CANONICAL_FORMATS + FORMAT_ALIASES) +FORMATS_LIST_TEXT = ", ".join(CANONICAL_FORMATS) + + +def fail(message, code=1): + """Print a diagnostics line to stderr and exit with `code`.""" + print("anydoc: " + message, file=sys.stderr) + raise SystemExit(code) + + +def emit_json(obj): + print(json.dumps(obj, ensure_ascii=False)) + + +def fail_command(args, command, message, code, error_class=None): + """Record a wrapper-level failure: diagnostics to stderr, JSON when asked.""" + print("anydoc: " + message, file=sys.stderr) + if getattr(args, "json", False): + emit_json( + { + "tool": TOOL_NAME, + "command": command, + "ok": False, + "exit_code": code, + "error": "anydoc: " + message, + "hint": None, + "error_class": error_class, + "output": getattr(args, "output", None), + } + ) + return code + + +# --- pre-validation --------------------------------------------------------- + +def validate_input(path): + """Return an error message for an unusable input path, or None.""" + if path == "-": + return None + target = Path(path) + if not target.exists(): + return "input file not found: %s" % path + if target.is_dir(): + return "input path is a directory, not a file: %s" % path + return None + + +def validate_output(path): + """Return an error message for an unusable -o path, or None.""" + if path is None: + return None + if Path(path).is_dir(): + return ( + "output path is a directory: %s (pass a file path; -o does not " + "create directories)" % path + ) + return None + + +def validate_format(fmt): + """Return an error message for an invalid -f value, or None.""" + if fmt is not None and fmt not in VALID_FORMATS: + return ( + "invalid format '%s'; expected one of: %s (extension aliases like " + "xls, docm, ppsx are accepted)" % (fmt, FORMATS_LIST_TEXT) + ) + return None + + +# --- runtime checks (node >= 20, npx) --------------------------------------- + +def node_version(): + """Return (major, full_version) for the node on PATH, or None.""" + node = shutil.which("node") + if node is None: + return None + try: + proc = subprocess.run( + [node, "--version"], capture_output=True, text=True, timeout=10 + ) + except OSError: + return None + if proc.returncode != 0: + return None + version = (proc.stdout or "").strip() + match = re.match(r"^v?(\d+)\.", version) + if match is None: + return None + return int(match.group(1)), version + + +def runtime_errors(): + """Return a list of node/npx problems, empty when the environment is ready.""" + errors = [] + info = node_version() + if info is None: + errors.append( + "Node.js >= %d is required but `node` was not found on PATH " + "(conversion runs via npx -y %s). Install Node.js >= %d and retry." + % (NODE_MIN_MAJOR, PINNED, NODE_MIN_MAJOR) + ) + else: + major, version = info + if major < NODE_MIN_MAJOR: + errors.append( + "Node.js version %s is too old; anydoc requires Node.js >= %d " + "(conversion runs via npx -y %s). Upgrade Node.js and retry." + % (version, NODE_MIN_MAJOR, PINNED) + ) + if shutil.which("npx") is None: + errors.append( + "`npx` was not found on PATH — conversion runs via `npx -y %s`. " + "Install Node.js >= %d (which ships npx), or install the CLI " + "permanently with `npm install -g %s`." + % (PINNED, NODE_MIN_MAJOR, CLI_PACKAGE) + ) + return errors + + +# --- CLI invocation --------------------------------------------------------- + +def build_cli_command(file, out, fmt): + """Build the exact argv the wrapper passes to the pinned CLI. + + Dash-leading filenames (a path like `-weird`) need the CLI's `--` + end-of-options marker so the file is read as the positional input. npx + forwards `--` to the CLI verbatim, so `-o`/`-f` must be placed BEFORE the + separator — after it they read as extra positional inputs ("unexpected + second input"). Absolute paths never start with `-` and take the plain + form. + """ + options = [] + if out: + options.extend(["-o", out]) + if fmt: + options.extend(["-f", fmt]) + argv = ["npx", "-y", PINNED] + if file != "-" and file.startswith("-"): + argv.extend(options) + argv.extend(["--", file]) + else: + argv.append(file) + argv.extend(options) + return argv + + +class CliTimeoutError(Exception): + """Raised when the pinned CLI does not complete within RUN_TIMEOUT seconds.""" + + def __init__(self, message): + super().__init__(message) + self.message = message + + +def run_cli(argv): + try: + return subprocess.run( + argv, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=RUN_TIMEOUT, + start_new_session=True, + ) + except subprocess.TimeoutExpired as exc: + try: + os.killpg(exc.pid, signal.SIGKILL) + except (ProcessLookupError, PermissionError): + pass + raise CliTimeoutError( + "conversion did not complete within %d seconds" % RUN_TIMEOUT + ) from exc + + +def error_class_hint(stderr): + """Map a failure message (CLI stderr or a wrapper pre-validation message) + to (error_class, hint-or-None).""" + if "document is encrypted" in stderr: + return ( + "encrypted", + "the document is encrypted or password-protected — supply an " + "unencrypted copy; anydoc has no password or decryption option.", + ) + if "OCR is required" in stderr or "no extractable text" in stderr: + return ( + "no-ocr", + "scanned or image-only PDF — anydoc does not perform OCR. Route " + "the file to OCR tooling or the hosted Firecrawl Parse API; do " + "not retry it locally.", + ) + if "malformed document" in stderr: + return ( + "malformed", + "the document is malformed or corrupt (not a readable zip " + "archive) — re-export or re-download the file and retry.", + ) + if "resource limit exceeded" in stderr: + return ("resource-limit", None) + if "EISDIR" in stderr: + return ( + "output-dir", + "the output path is a directory — pass a file path (-o does not " + "create directories).", + ) + if "unsupported input" in stderr: + return ( + "unsupported", + "unsupported or unrecognized file type — check that the extension " + "is one of the supported formats, or force it with -f .", + ) + if "io error" in stderr: + return ( + "io", + "the file could not be read — check that the path exists and is " + "readable.", + ) + # Wrapper pre-validation messages (batch per-file entries). + if "input file not found" in stderr or "input path is a directory" in stderr: + return ("io", None) + return ("unknown", None) + + +def print_cli_error(stderr): + if not stderr: + stderr = "conversion failed" + line = stderr if stderr.startswith("anydoc: ") else "anydoc: " + stderr + print(line, file=sys.stderr) + + +# --- subcommands ------------------------------------------------------------ + +def cmd_convert(args): + error = validate_input(args.file) + if error is not None: + return fail_command(args, "convert", error, 1) + error = validate_output(args.output) + if error is not None: + return fail_command(args, "convert", error, 1) + error = validate_format(args.format) + if error is not None: + return fail_command(args, "convert", error, 2) + + command = build_cli_command(args.file, args.output, args.format) + if args.dry_run: + plan = " ".join(command) + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "convert", + "ok": True, + "exit_code": 0, + "dry_run": True, + "input": args.file, + "output": args.output, + "command_line": plan, + } + ) + else: + print("dry-run: would run: %s" % plan) + return 0 + + for error in runtime_errors(): + return fail_command(args, "convert", error, 1) + + try: + proc = run_cli(command) + except CliTimeoutError as err: + return fail_command(args, "convert", err.message, 1, error_class="timeout") + if proc.returncode == 0: + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "convert", + "ok": True, + "exit_code": 0, + "output": args.output, + "markdown": None if args.output else proc.stdout, + } + ) + elif not args.output: + sys.stdout.write(proc.stdout) + return 0 + + stderr = (proc.stderr or "").strip() + error_class, hint = error_class_hint(stderr) + print_cli_error(stderr) + if hint is not None: + print("anydoc: hint: " + hint, file=sys.stderr) + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "convert", + "ok": False, + "exit_code": proc.returncode, + "error": stderr, + "hint": hint, + "error_class": error_class, + "output": args.output, + } + ) + return proc.returncode + + +def cmd_batch(args): + out_dir = Path(args.out_dir) if args.out_dir else Path.cwd() + + records = [] + for input_path in args.inputs: + error = validate_input(input_path) + records.append( + { + "input": input_path, + "output": str(out_dir / (Path(input_path).stem + ".md")), + "error": error, + } + ) + + if args.dry_run: + plan = [] + for record in records: + plan.append( + { + "input": record["input"], + "output": record["output"], + "command": " ".join( + build_cli_command(record["input"], record["output"], None) + ), + "would_fail": record["error"] is not None, + "error": record["error"], + } + ) + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "batch", + "ok": True, + "exit_code": 0, + "dry_run": True, + "out_dir": str(out_dir), + "plan": plan, + } + ) + else: + for entry in plan: + marker = ( + " [would fail: %s]" % entry["error"] if entry["error"] else "" + ) + print( + "plan: convert %s -> %s%s" + % (entry["input"], entry["output"], marker) + ) + print(" command: %s" % entry["command"]) + return 0 + + if out_dir.exists() and not out_dir.is_dir(): + return fail_command( + args, + "batch", + "output directory path is not a directory: %s" % out_dir, + 1, + ) + + for error in runtime_errors(): + return fail_command(args, "batch", error, 1) + + out_dir.mkdir(parents=True, exist_ok=True) + + results = [] + for record in records: + if record["error"] is not None: + error_class, hint = error_class_hint(record["error"]) + results.append( + { + "input": record["input"], + "output": record["output"], + "status": "failed", + "error": "anydoc: " + record["error"], + "hint": hint, + "error_class": error_class, + } + ) + print("anydoc: " + record["error"], file=sys.stderr) + if not args.json: + print("FAIL %s" % record["input"]) + continue + try: + proc = run_cli(build_cli_command(record["input"], record["output"], None)) + except CliTimeoutError as err: + return fail_command(args, "batch", err.message, 1, error_class="timeout") + if proc.returncode == 0: + results.append( + { + "input": record["input"], + "output": record["output"], + "status": "ok", + "error": None, + "hint": None, + } + ) + if not args.json: + print("ok %s -> %s" % (record["input"], record["output"])) + else: + stderr = (proc.stderr or "").strip() + error_class, hint = error_class_hint(stderr) + results.append( + { + "input": record["input"], + "output": record["output"], + "status": "failed", + "error": stderr, + "hint": hint, + "error_class": error_class, + } + ) + print_cli_error(stderr) + if hint is not None: + print("anydoc: hint: " + hint, file=sys.stderr) + if not args.json: + print("FAIL %s" % record["input"]) + + succeeded = sum(1 for r in results if r["status"] == "ok") + failed = len(results) - succeeded + if not args.json: + print( + "summary: %d total, %d succeeded, %d failed" + % (len(results), succeeded, failed) + ) + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "batch", + "ok": failed == 0, + "exit_code": 1 if failed else 0, + "dry_run": False, + "out_dir": str(out_dir), + "files": results, + "summary": { + "total": len(results), + "succeeded": succeeded, + "failed": failed, + }, + } + ) + return 1 if failed else 0 + + +def cmd_info(args): + if args.version: + print(CLI_VERSION) + return 0 + if args.json: + emit_json( + { + "tool": TOOL_NAME, + "command": "info", + "ok": True, + "exit_code": 0, + "name": TOOL_NAME, + "cli": PINNED, + "version": CLI_VERSION, + } + ) + else: + print("%s %s (wraps %s)" % (TOOL_NAME, CLI_VERSION, PINNED)) + return 0 + + +# --- CLI plumbing ----------------------------------------------------------- + +def extract_globals(argv): + """Hoist --json / --dry-run to the front so they work anywhere in argv.""" + values, retained = [], [] + for arg in argv: + if arg in ("--json", "--dry-run"): + values.append(arg) + else: + retained.append(arg) + return values + retained + + +def build_parser(): + parser = argparse.ArgumentParser( + prog="anydoc", + description=( + "Convert office documents to GitHub-Flavored Markdown locally via " + "the pinned Any Doc CLI (%s)." % PINNED + ), + epilog=( + "Examples:\n" + " anydoc convert report.docx\n" + " anydoc convert report.docx -o report.md\n" + " anydoc convert - -f csv < data.csv\n" + " anydoc batch a.docx b.csv --out-dir out/\n" + " anydoc info\n" + "\nExit codes: 0 success / 1 conversion or pre-validation failure " + "/ 2 usage error. JSON goes to stdout; diagnostics go to stderr. " + "The wrapper never prompts (npx runs with -y)." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--json", + action="store_true", + help="Emit exactly one JSON document on stdout; diagnostics stay on stderr.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Print what would run and execute nothing (no CLI spawn, no output files).", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + convert = subparsers.add_parser( + "convert", + help="Convert one document (path or - for stdin) to markdown.", + description=( + "Convert one document to GitHub-Flavored Markdown. Pass - as the " + "input to read the document from stdin (CSV from stdin needs -f csv)." + ), + epilog=( + "Examples:\n" + " anydoc convert report.docx\n" + " anydoc convert report.docx -o report.md\n" + " anydoc convert - -f csv < data.csv\n" + "\nWith --json the converted markdown is embedded in the JSON " + "document when -o is not given. Exit codes: 0 success / 1 " + "conversion or pre-validation failure / 2 usage error." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + convert.add_argument( + "file", metavar="", help="Document path, or - to read from stdin." + ) + convert.add_argument( + "-o", + "--output", + metavar="out.md", + help="Write the markdown to this file instead of stdout (silently overwrites).", + ) + convert.add_argument( + "-f", + "--format", + metavar="", + help=( + "Force the input format instead of detecting it: one of %s " + "(extension aliases like xls, docm, ppsx are accepted)." + % FORMATS_LIST_TEXT + ), + ) + + batch = subparsers.add_parser( + "batch", + help="Convert many documents, one at a time, to an output directory.", + description=( + "Convert many documents to markdown files. Each input is converted " + "independently; the batch continues past failures and prints a " + "summary. Output naming is deterministic: .md in --out-dir " + "(created when missing; default: the current working directory)." + ), + epilog=( + "Examples:\n" + " anydoc batch a.docx b.xlsx c.csv --out-dir out/\n" + " anydoc batch notes/*.docx --out-dir vault/inbox/ --dry-run --json\n" + "\nDuplicate inputs convert per occurrence (a later conversion " + "overwrites the earlier output); same-basename inputs from " + "different directories collide on the same .md and the last " + "one wins. Exit codes: 0 when every input converted; 1 when any " + "input failed; 2 for a usage error." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + batch.add_argument( + "inputs", nargs="+", metavar="", help="Document paths to convert." + ) + batch.add_argument( + "--out-dir", + metavar="DIR", + help="Directory for the converted .md files (created when missing; default: the current directory).", + ) + + info = subparsers.add_parser( + "info", + help="Show the tool name and the pinned CLI version.", + description="Report the wrapper tool name and the pinned Any Doc CLI version.", + epilog=( + "Examples:\n" + " anydoc info\n" + " anydoc info --version\n" + "\n--version prints exactly the pinned CLI version (0.1.6)." + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + info.add_argument( + "--version", + action="store_true", + help="Print only the pinned CLI version and exit.", + ) + return parser + + +def main(argv=None): + parser = build_parser() + args = parser.parse_args( + extract_globals(list(argv if argv is not None else sys.argv[1:])) + ) + if args.command == "convert": + return cmd_convert(args) + if args.command == "batch": + return cmd_batch(args) + if args.command == "info": + return cmd_info(args) + parser.print_help() + return 0 + + +if __name__ == "__main__": + try: + code = main() + except BrokenPipeError: + code = 0 + try: + sys.exit(code) + except BrokenPipeError: + # Downstream pipe closed early (e.g. `anydoc convert big.docx | head`) + # — exit 0, mirroring the CLI's EPIPE behavior, without stderr noise. + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(0) diff --git a/anydoc/tests/test_anydoc.py b/anydoc/tests/test_anydoc.py new file mode 100644 index 0000000..7ff8f44 --- /dev/null +++ b/anydoc/tests/test_anydoc.py @@ -0,0 +1,810 @@ +#!/usr/bin/env python3 +"""Unit tests for the anydoc wrapper (`anydoc/scripts/anydoc`). + +Offline by design: the core tests need no node, no npx, and no network. +Real-CLI tests (converting the committed fixtures through the pinned CLI) +are opt-in and skip gracefully when the toolchain is unavailable. +""" + +import importlib.machinery +import io +import json +import os +import shutil +import signal +import stat +import subprocess +import sys +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path +from unittest import mock + +ROOT = Path(__file__).resolve().parents[1] # anydoc/ +SCRIPT = ROOT / "scripts" / "anydoc" +FIXTURES = ROOT / "fixtures" + +DOCX = FIXTURES / "fixture-handmade-outline.docx" +CSV = FIXTURES / "fixture-sheet.csv" +SCANNED = FIXTURES / "scanned-image-only.pdf" +ENCRYPTED = FIXTURES / "encrypted--errors.odt" +MALFORMED = FIXTURES / "empty--errors.docx" +UNSUPPORTED = FIXTURES / "unsupported.xyz" +TABLES = FIXTURES / "fixture-handmade-tables.docx" + +PINNED = "@firecrawl/anydoc@0.1.6" + +cli = importlib.machinery.SourceFileLoader("anydoc_wrapper", str(SCRIPT)).load_module() + + +def run_in_process(arguments): + """Run cli.main() in-process; return (code, stdout, stderr).""" + stdout, stderr = io.StringIO(), io.StringIO() + with redirect_stdout(stdout), redirect_stderr(stderr): + try: + code = cli.main(arguments) + except SystemExit as exc: + code = exc.code if exc.code is not None else 0 + return code, stdout.getvalue(), stderr.getvalue() + + +def run_script(arguments, env=None, cwd=None, input_bytes=None, timeout=120): + """Run the wrapper as a subprocess; return CompletedProcess.""" + return subprocess.run( + [sys.executable, str(SCRIPT)] + arguments, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + env=env, + cwd=cwd, + input=input_bytes, + timeout=timeout, + ) + + +def minimal_path_env(): + """A PATH containing only a python3 symlink (no node, no npx).""" + tmp = Path(tempfile.mkdtemp()) + bindir = tmp / "bin" + bindir.mkdir() + os.symlink(sys.executable, bindir / "python3") + env = os.environ.copy() + env["PATH"] = str(bindir) + return tmp, env + + +def node_shim_env(version_line): + """A PATH whose `node` is a shim printing `version_line`.""" + tmp, env = minimal_path_env() + bindir = tmp / "bin2" + bindir.mkdir() + shim = bindir / "node" + shim.write_text("#!/bin/sh\n%s\n" % version_line) + shim.chmod(0o755) + env["PATH"] = str(bindir) + os.pathsep + env["PATH"] + return tmp, env + + +class WrapperCoreTests(unittest.TestCase): + """Offline wrapper behavior: help, usage errors, pre-validation, plans.""" + + def test_script_is_executable_and_has_shebang(self): + mode = stat.S_IMODE(SCRIPT.stat().st_mode) + self.assertTrue(mode & stat.S_IXUSR, "scripts/anydoc must be executable") + with SCRIPT.open("rb") as handle: + first = handle.readline().decode("utf-8", "replace").strip() + self.assertEqual(first, "#!/usr/bin/env python3") + + def test_direct_execution_via_shebang(self): + result = subprocess.run( + [str(SCRIPT), "info"], capture_output=True, text=True, timeout=60 + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("anydoc", result.stdout) + self.assertIn("0.1.6", result.stdout) + + def test_help_exits_zero_with_usage_and_examples(self): + for arguments in ( + ["--help"], + ["convert", "--help"], + ["batch", "--help"], + ["info", "--help"], + ): + with self.subTest(arguments=arguments): + code, stdout, stderr = run_in_process(arguments) + self.assertEqual(code, 0, stderr) + self.assertIn("usage", stdout.lower()) + self.assertIn("Example", stdout) + self.assertEqual(stderr, "") + + def test_batch_help_documents_exit_semantics(self): + _, stdout, _ = run_in_process(["batch", "--help"]) + self.assertIn("1 when any", stdout) + self.assertIn("input failed", stdout) + + def test_help_works_without_node_on_path(self): + tmp, env = minimal_path_env() + try: + for arguments in ( + ["--help"], + ["convert", "--help"], + ["batch", "--help"], + ["info", "--help"], + ): + with self.subTest(arguments=arguments): + result = run_script(arguments, env=env) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("usage", result.stdout.lower()) + self.assertIn("Example", result.stdout) + self.assertEqual(result.stderr, "") + finally: + shutil.rmtree(tmp) + + def test_usage_errors_exit_2(self): + # Each usage error must exit 2 on stderr and name the offending token + # OR the missing input (argparse names the missing subcommand for an + # unknown root option). + cases = ( + (["--bogus"], "command"), + (["convert", str(DOCX), "--bogus"], "--bogus"), + (["convert"], "required"), + (["batch"], "required"), + ) + for arguments, needle in cases: + with self.subTest(arguments=arguments): + code, stdout, stderr = run_in_process(arguments) + self.assertEqual(code, 2) + self.assertEqual(stdout, "") + self.assertIn(needle, stderr) + self.assertIn("usage", stderr.lower()) + self.assertNotIn("Traceback", stderr) + + def test_info_reports_tool_and_version(self): + code, stdout, stderr = run_in_process(["info"]) + self.assertEqual(code, 0, stderr) + self.assertIn("anydoc", stdout) + self.assertIn("0.1.6", stdout) + self.assertEqual(stderr, "") + + def test_info_version_prints_exact_version(self): + code, stdout, stderr = run_in_process(["info", "--version"]) + self.assertEqual(code, 0, stderr) + self.assertEqual(stdout.strip(), "0.1.6") + self.assertEqual(stderr, "") + + def test_convert_missing_input_prevalidation(self): + code, stdout, stderr = run_in_process( + ["convert", "/nonexistent/anydoc-input.docx"] + ) + self.assertEqual(code, 1) + self.assertEqual(stdout, "") + self.assertIn("/nonexistent/anydoc-input.docx", stderr) + self.assertNotIn("Traceback", stderr) + + def test_convert_directory_input_prevalidation(self): + code, stdout, stderr = run_in_process(["convert", str(FIXTURES)]) + self.assertEqual(code, 1) + self.assertEqual(stdout, "") + self.assertIn(str(FIXTURES), stderr) + self.assertIn("directory", stderr) + self.assertNotIn("Traceback", stderr) + + def test_convert_output_path_is_directory(self): + code, stdout, stderr = run_in_process( + ["convert", str(DOCX), "-o", str(FIXTURES)] + ) + self.assertEqual(code, 1) + self.assertEqual(stdout, "") + self.assertIn("directory", stderr) + self.assertNotIn("Traceback", stderr) + + def test_convert_invalid_format_exit_2(self): + code, stdout, stderr = run_in_process( + ["convert", str(DOCX), "-f", "bogus"] + ) + self.assertEqual(code, 2) + self.assertEqual(stdout, "") + self.assertIn("invalid format 'bogus'", stderr) + self.assertNotIn("Traceback", stderr) + + def test_convert_dry_run_plans_without_executing(self): + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "out.md" + code, stdout, stderr = run_in_process( + ["convert", str(DOCX), "-o", str(out), "--dry-run"] + ) + self.assertEqual(code, 0, stderr) + self.assertIn("npx -y " + PINNED, stdout) + self.assertIn(str(DOCX), stdout) + self.assertEqual(stderr, "") + self.assertFalse(out.exists(), "dry-run must not create outputs") + + def test_convert_dry_run_json(self): + code, stdout, stderr = run_in_process( + ["convert", str(DOCX), "--dry-run", "--json"] + ) + self.assertEqual(code, 0, stderr) + doc = json.loads(stdout) + self.assertTrue(doc["dry_run"]) + self.assertEqual(doc["command"], "convert") + self.assertIn("npx -y " + PINNED, doc["command_line"]) + self.assertEqual(stderr, "") + + def test_batch_dry_run_json_plan_and_no_output_dir(self): + with tempfile.TemporaryDirectory() as tmp: + out_dir = Path(tmp) / "out" + code, stdout, stderr = run_in_process( + [ + "batch", + str(DOCX), + str(CSV), + "--out-dir", + str(out_dir), + "--dry-run", + "--json", + ] + ) + self.assertEqual(code, 0, stderr) + doc = json.loads(stdout) + self.assertTrue(doc["dry_run"]) + self.assertEqual(doc["command"], "batch") + self.assertEqual(len(doc["plan"]), 2) + for entry in doc["plan"]: + self.assertIn("input", entry) + self.assertIn("output", entry) + self.assertIn("command", entry) + self.assertIn("npx -y " + PINNED, entry["command"]) + self.assertEqual(stderr, "") + self.assertFalse(out_dir.exists(), "dry-run must not create out-dir") + + def test_batch_dry_run_marks_invalid_inputs(self): + with tempfile.TemporaryDirectory() as tmp: + missing = Path(tmp) / "missing.docx" + code, stdout, stderr = run_in_process( + [ + "batch", + str(DOCX), + str(missing), + "--out-dir", + str(Path(tmp) / "out"), + "--dry-run", + "--json", + ] + ) + self.assertEqual(code, 0, stderr) + doc = json.loads(stdout) + self.assertEqual(len(doc["plan"]), 2) + self.assertFalse(doc["plan"][0]["would_fail"]) + self.assertTrue(doc["plan"][1]["would_fail"]) + self.assertIn("not found", doc["plan"][1]["error"]) + + def test_batch_dry_run_defaults_out_dir_to_cwd(self): + with tempfile.TemporaryDirectory() as tmp: + code, stdout, stderr = run_in_process( + [ + "batch", + str(DOCX), + "--dry-run", + "--json", + ], + ) + self.assertEqual(code, 0, stderr) + doc = json.loads(stdout) + self.assertEqual(doc["out_dir"], str(Path.cwd())) + + def test_batch_requires_inputs(self): + code, stdout, stderr = run_in_process(["batch"]) + self.assertEqual(code, 2) + self.assertEqual(stdout, "") + + def test_convert_json_error_stays_parseable(self): + code, stdout, stderr = run_in_process( + ["convert", "/nonexistent/anydoc-input.docx", "--json"] + ) + self.assertEqual(code, 1) + doc = json.loads(stdout) + self.assertFalse(doc["ok"]) + self.assertEqual(doc["exit_code"], 1) + self.assertIn("not found", doc["error"]) + self.assertIn("not found", stderr) + + def test_node_missing_error_via_minimal_path(self): + tmp, env = minimal_path_env() + try: + result = run_script(["convert", str(DOCX)], env=env) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("node", result.stderr.lower()) + self.assertIn("20", result.stderr) + self.assertNotIn("Traceback", result.stderr) + finally: + shutil.rmtree(tmp) + + def test_node_too_old_error_via_shim(self): + tmp, env = node_shim_env('echo "v18.20.0"') + try: + result = run_script(["convert", str(DOCX)], env=env) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("v18.20.0", result.stderr) + self.assertIn("20", result.stderr) + self.assertNotIn("Traceback", result.stderr) + finally: + shutil.rmtree(tmp) + + @unittest.skipUnless(shutil.which("node"), "node not on PATH") + def test_npx_missing_error_via_path_with_node(self): + tmp = Path(tempfile.mkdtemp()) + try: + bindir = tmp / "bin" + bindir.mkdir() + os.symlink(sys.executable, bindir / "python3") + node_bin = tmp / "bin2" + node_bin.mkdir() + os.symlink(Path(shutil.which("node")), node_bin / "node") + env = os.environ.copy() + env["PATH"] = str(node_bin) + os.pathsep + str(bindir) + result = run_script(["convert", str(DOCX)], env=env) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("npx", result.stderr) + self.assertIn(PINNED, result.stderr) + self.assertNotIn("Traceback", result.stderr) + finally: + shutil.rmtree(tmp) + + def test_hint_mapping_for_known_error_classes(self): + cases = ( + ( + "anydoc: unsupported input: PDF has no extractable text " + "(Scanned, 1 pages): OCR is required", + "no-ocr", + ("OCR", "Firecrawl Parse", "not retry"), + ), + ("anydoc: document is encrypted", "encrypted", ("encrypted", "unencrypted")), + ( + "anydoc: malformed document: not a readable zip archive: " + "invalid Zip archive: Could not find EOCD", + "malformed", + ("malformed", "corrupt", "zip"), + ), + ( + "anydoc: unsupported input: unrecognized file content and " + "extension: unsupported.xyz", + "unsupported", + ("unsupported", "-f"), + ), + ( + "anydoc: resource limit exceeded (max_entry_bytes): " + "word/document.xml declares 201326759 decompressed bytes", + "resource-limit", + (), + ), + # Wrapper pre-validation messages map to the "io" class with no hint. + ("input file not found: /x/missing.docx", "io", ()), + ("input path is a directory, not a file: /x/dir", "io", ()), + ) + for message, expected_class, keywords in cases: + with self.subTest(message=message): + error_class, hint = cli.error_class_hint(message) + self.assertEqual(error_class, expected_class) + if keywords: + self.assertIsNotNone(hint) + for keyword in keywords: + self.assertIn(keyword, hint) + + def test_build_cli_command_shape(self): + self.assertEqual( + cli.build_cli_command("report.docx", "out.md", "csv"), + ["npx", "-y", PINNED, "report.docx", "-o", "out.md", "-f", "csv"], + ) + self.assertEqual( + cli.build_cli_command("report.docx", None, None), + ["npx", "-y", PINNED, "report.docx"], + ) + # stdin passes through as `-` + self.assertEqual( + cli.build_cli_command("-", None, "csv"), + ["npx", "-y", PINNED, "-", "-f", "csv"], + ) + # a dash-leading filename places -o/-f BEFORE the `--` separator + # (npx forwards `--` to the CLI, so options after it read as inputs) + self.assertEqual( + cli.build_cli_command("-weird", "o.md", "csv"), + ["npx", "-y", PINNED, "-o", "o.md", "-f", "csv", "--", "-weird"], + ) + self.assertEqual( + cli.build_cli_command("-weird", None, None), + ["npx", "-y", PINNED, "--", "-weird"], + ) + + @unittest.skipUnless(shutil.which("node") and shutil.which("npx"), "toolchain missing") + def test_runtime_errors_empty_when_toolchain_present(self): + self.assertEqual(cli.runtime_errors(), []) + + def test_format_aliases_accepted(self): + code, _stdout, stderr = run_in_process( + ["convert", str(DOCX), "-f", "docm", "--dry-run"] + ) + self.assertEqual(code, 0, stderr) + + # --- CLI timeout: --json must still yield one parseable JSON document --- + + def _timeout_side_effect(self): + """A subprocess.run replacement that raises a TimeoutExpired.""" + + def _boom(*args, **kwargs): + exc = subprocess.TimeoutExpired( + cmd=args[0], timeout=cli.RUN_TIMEOUT + ) + exc.pid = 4242 # set post-construction, as subprocess.run does + raise exc + + return _boom + + def test_run_cli_timeout_kills_group_and_raises(self): + with mock.patch.object( + cli.subprocess, "run", side_effect=self._timeout_side_effect() + ), mock.patch.object(cli.os, "killpg") as mock_kill: + with self.assertRaises(cli.CliTimeoutError): + cli.run_cli(["npx", "-y", cli.PINNED, "x.docx"]) + mock_kill.assert_called_once_with(4242, signal.SIGKILL) + + def test_convert_timeout_with_json_emits_error_envelope(self): + with mock.patch.object(cli, "runtime_errors", return_value=[]), mock.patch.object( + cli.subprocess, "run", side_effect=self._timeout_side_effect() + ), mock.patch.object(cli.os, "killpg") as mock_kill: + code, stdout, stderr = run_in_process( + ["convert", str(DOCX), "--json"] + ) + self.assertEqual(code, 1) + mock_kill.assert_called_once_with(4242, signal.SIGKILL) + doc = json.loads(stdout) # exactly one parseable JSON document + self.assertFalse(doc["ok"]) + self.assertEqual(doc["exit_code"], 1) + self.assertEqual(doc["error_class"], "timeout") + self.assertIn("did not complete within 120 seconds", doc["error"]) + self.assertIn("did not complete within 120 seconds", stderr) + self.assertNotIn("Traceback", stderr) + + def test_convert_timeout_without_json_uses_stderr(self): + with mock.patch.object(cli, "runtime_errors", return_value=[]), mock.patch.object( + cli.subprocess, "run", side_effect=self._timeout_side_effect() + ), mock.patch.object(cli.os, "killpg"): + code, stdout, stderr = run_in_process(["convert", str(DOCX)]) + self.assertEqual(code, 1) + self.assertEqual(stdout, "") + self.assertIn("did not complete within 120 seconds", stderr) + self.assertNotIn("Traceback", stderr) + + def test_batch_timeout_with_json_emits_error_envelope(self): + with tempfile.TemporaryDirectory() as tmp: + out_dir = Path(tmp) / "out" + with mock.patch.object( + cli, "runtime_errors", return_value=[] + ), mock.patch.object( + cli.subprocess, "run", side_effect=self._timeout_side_effect() + ), mock.patch.object(cli.os, "killpg"): + code, stdout, stderr = run_in_process( + ["batch", str(DOCX), "--out-dir", str(out_dir), "--json"] + ) + self.assertEqual(code, 1) + doc = json.loads(stdout) + self.assertFalse(doc["ok"]) + self.assertEqual(doc["command"], "batch") + self.assertEqual(doc["error_class"], "timeout") + self.assertIn("did not complete within 120 seconds", stderr) + self.assertNotIn("Traceback", stderr) + + +class RealCliTests(unittest.TestCase): + """End-to-end conversions through the pinned CLI; skip when unavailable.""" + + skip_reason = None + + @classmethod + def setUpClass(cls): + if not shutil.which("npx") or not shutil.which("node"): + cls.skip_reason = "npx/node not available" + return + try: + proc = subprocess.run( + ["npx", "-y", PINNED, "--version"], + capture_output=True, + text=True, + timeout=120, + ) + except (OSError, subprocess.TimeoutExpired): + cls.skip_reason = "pinned CLI unavailable" + return + if proc.returncode != 0 or "0.1.6" not in proc.stdout: + cls.skip_reason = "pinned CLI unavailable" + return + cls.skip_reason = None + + def setUp(self): + if self.__class__.skip_reason: + self.skipTest(self.__class__.skip_reason) + + def _parse_ok_json(self, result): + """Assert a successful --json run and return its parsed document.""" + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stderr, "") + doc = json.loads(result.stdout) + self.assertTrue(doc["ok"]) + self.assertEqual(doc["exit_code"], 0) + return doc + + def test_convert_to_stdout(self): + result = run_script(["convert", str(DOCX)]) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stderr, "") + self.assertIn("# ", result.stdout) + self.assertIn("## ", result.stdout) + + def test_convert_to_file_silent(self): + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "out.md" + result = run_script(["convert", str(DOCX), "-o", str(out)]) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, "") + self.assertEqual(result.stderr, "") + self.assertTrue(out.exists()) + content = out.read_text(encoding="utf-8") + self.assertIn("## ", content) + + def test_convert_silently_overwrites_seeded_file(self): + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "out.md" + out.write_text("SENTINEL\n", encoding="utf-8") + result = run_script(["convert", str(DOCX), "-o", str(out)]) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, "") + self.assertEqual(result.stderr, "") + content = out.read_text(encoding="utf-8") + self.assertNotIn("SENTINEL", content) + self.assertIn("## ", content) + + def test_convert_fresh_cwd_creates_no_stray_files(self): + with tempfile.TemporaryDirectory() as tmp: + result = run_script(["convert", str(DOCX)], cwd=tmp) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stderr, "") + self.assertFalse((Path(tmp) / "out.md").exists()) + self.assertEqual(list(Path(tmp).iterdir()), []) + + def test_convert_stdin_csv(self): + result = run_script( + ["convert", "-", "-f", "csv"], input_bytes="a,b\n1,2\n" + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("| a | b |", result.stdout) + self.assertIn("| 1 | 2 |", result.stdout) + + def test_convert_empty_stdin_errors_without_hanging(self): + result = run_script(["convert", "-"], input_bytes="") + self.assertNotEqual(result.returncode, 0) + self.assertIn("anydoc", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_convert_extensionless_file_with_fmt_csv(self): + with tempfile.TemporaryDirectory() as tmp: + data = Path(tmp) / "data" + data.write_bytes(CSV.read_bytes()) + result = run_script(["convert", str(data), "-f", "csv"]) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("| Kind | Value | Note |", result.stdout) + + def test_convert_scanned_pdf_hint(self): + result = run_script(["convert", str(SCANNED)]) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("OCR", result.stderr) + self.assertIn("Firecrawl Parse", result.stderr) + self.assertIn("not retry", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_convert_encrypted_hint(self): + result = run_script(["convert", str(ENCRYPTED)]) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("encrypted", result.stderr) + self.assertIn("unencrypted", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_convert_malformed_hint(self): + result = run_script(["convert", str(MALFORMED)]) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("malformed", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_convert_unsupported_hint(self): + result = run_script(["convert", str(UNSUPPORTED)]) + self.assertEqual(result.returncode, 1) + self.assertEqual(result.stdout, "") + self.assertIn("unsupported", result.stderr) + self.assertIn("-f", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_convert_json_success_to_file(self): + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "out.md" + result = run_script( + ["convert", str(DOCX), "-o", str(out), "--json"] + ) + doc = self._parse_ok_json(result) + self.assertEqual(doc["output"], str(out)) + + def test_convert_json_success_embeds_markdown(self): + result = run_script(["convert", str(DOCX), "--json"]) + doc = self._parse_ok_json(result) + self.assertIn("## ", doc["markdown"]) + + def test_convert_json_failure(self): + result = run_script(["convert", str(SCANNED), "--json"]) + self.assertEqual(result.returncode, 1) + doc = json.loads(result.stdout) + self.assertFalse(doc["ok"]) + self.assertEqual(doc["exit_code"], 1) + self.assertEqual(doc["error_class"], "no-ocr") + self.assertIn("OCR", result.stderr) + + def test_batch_mixed_continues_past_failures(self): + with tempfile.TemporaryDirectory() as tmp: + out_dir = Path(tmp) / "out" + result = run_script( + [ + "batch", + str(DOCX), + str(ENCRYPTED), + str(TABLES), + "--out-dir", + str(out_dir), + ] + ) + self.assertEqual(result.returncode, 1) + stdout = result.stdout + self.assertIn("ok %s" % DOCX, stdout) + self.assertIn("FAIL %s" % ENCRYPTED, stdout) + self.assertIn("ok %s" % TABLES, stdout) + self.assertIn("summary: 3 total, 2 succeeded, 1 failed", stdout) + self.assertTrue((out_dir / "fixture-handmade-outline.md").exists()) + self.assertTrue((out_dir / "fixture-handmade-tables.md").exists()) + self.assertFalse((out_dir / "encrypted--errors.md").exists()) + self.assertIn("encrypted", result.stderr) + self.assertIn("hint", result.stderr) + + def test_batch_all_valid_exits_zero(self): + with tempfile.TemporaryDirectory() as tmp: + out_dir = Path(tmp) / "out" + result = run_script( + [ + "batch", + str(DOCX), + str(CSV), + "--out-dir", + str(out_dir), + ] + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stderr, "") + self.assertIn("summary: 2 total, 2 succeeded, 0 failed", result.stdout) + self.assertTrue((out_dir / "fixture-handmade-outline.md").exists()) + self.assertTrue((out_dir / "fixture-sheet.md").exists()) + + def test_batch_json_all_valid(self): + with tempfile.TemporaryDirectory() as tmp: + result = run_script( + [ + "batch", + str(DOCX), + str(CSV), + "--out-dir", + str(Path(tmp) / "out"), + "--json", + ] + ) + doc = self._parse_ok_json(result) + self.assertEqual(doc["summary"], {"total": 2, "succeeded": 2, "failed": 0}) + self.assertEqual([f["status"] for f in doc["files"]], ["ok", "ok"]) + + def test_batch_json_mixed_keeps_stdout_parseable(self): + with tempfile.TemporaryDirectory() as tmp: + result = run_script( + [ + "batch", + str(DOCX), + str(ENCRYPTED), + "--out-dir", + str(Path(tmp) / "out"), + "--json", + ] + ) + self.assertEqual(result.returncode, 1) + doc = json.loads(result.stdout) + self.assertFalse(doc["ok"]) + self.assertEqual(doc["exit_code"], 1) + self.assertEqual(doc["summary"], {"total": 2, "succeeded": 1, "failed": 1}) + self.assertIn("encrypted", result.stderr) + + def test_batch_json_failure_entries_share_error_class_shape(self): + with tempfile.TemporaryDirectory() as tmp: + missing = Path(tmp) / "missing.docx" + result = run_script( + [ + "batch", + str(ENCRYPTED), + str(missing), + "--out-dir", + str(Path(tmp) / "out"), + "--json", + ] + ) + self.assertEqual(result.returncode, 1) + doc = json.loads(result.stdout) + by_input = {entry["input"]: entry for entry in doc["files"]} + cli_fail = by_input[str(ENCRYPTED)] + pre_fail = by_input[str(missing)] + self.assertEqual(cli_fail["status"], "failed") + self.assertEqual(cli_fail["error_class"], "encrypted") + self.assertEqual(pre_fail["status"], "failed") + self.assertEqual(pre_fail["error_class"], "io") + self.assertEqual( + set(cli_fail.keys()), + set(pre_fail.keys()), + "all batch failure entries must share the same shape", + ) + + def test_convert_dash_leading_filename(self): + with tempfile.TemporaryDirectory() as tmp: + (Path(tmp) / "-weird").write_bytes(CSV.read_bytes()) + result = run_script( + ["convert", "-f", "csv", "--", "-weird"], + cwd=tmp, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("| Kind | Value | Note |", result.stdout) + + def test_batch_duplicates_convert_per_occurrence(self): + with tempfile.TemporaryDirectory() as tmp: + out_dir = Path(tmp) / "out" + result = run_script( + ["batch", str(DOCX), str(DOCX), "--out-dir", str(out_dir)] + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("summary: 2 total, 2 succeeded, 0 failed", result.stdout) + self.assertEqual( + sorted(p.name for p in out_dir.iterdir()), ["fixture-handmade-outline.md"] + ) + + def test_batch_same_basename_collision_last_wins(self): + with tempfile.TemporaryDirectory() as tmp: + a_dir = Path(tmp) / "a" + b_dir = Path(tmp) / "b" + a_dir.mkdir() + b_dir.mkdir() + (a_dir / "same.docx").write_bytes(DOCX.read_bytes()) + (b_dir / "same.docx").write_bytes(TABLES.read_bytes()) + out_dir = Path(tmp) / "out" + result = run_script( + [ + "batch", + str(a_dir / "same.docx"), + str(b_dir / "same.docx"), + "--out-dir", + str(out_dir), + ] + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual( + sorted(p.name for p in out_dir.iterdir()), ["same.md"] + ) + content = (out_dir / "same.md").read_text(encoding="utf-8") + self.assertIn("| Head A | Head B | Head C |", content) + + +if __name__ == "__main__": + unittest.main() diff --git a/documents/SKILL.md b/documents/SKILL.md index feace13..5f96682 100644 --- a/documents/SKILL.md +++ b/documents/SKILL.md @@ -60,6 +60,10 @@ Load this skill when the task involves any of the four formats: documentation-site conventions, not office documents. - **Data pipelines** — moving or transforming raw data belongs to `data-engineering`; Excel here is a *deliverable format*, not a data store. +- **Office documents to Markdown** — converting an existing office document + (docx, xlsx, pptx, pdf, odt, rtf, epub, csv) to GitHub-Flavored Markdown + belongs to the `anydoc` skill; this skill owns generation, editing, and + validation, not document-to-markdown extraction. ## The Shared Workflow diff --git a/llms.txt b/llms.txt index 59e6477..d97ba01 100644 --- a/llms.txt +++ b/llms.txt @@ -7,6 +7,7 @@ - [agent-evals-and-observability](agent-evals-and-observability/SKILL.md): Design, run, review, or release framework- and vendor-neutral evaluations and observability for AI agents. Use when defining agent evals, datasets, graders, trajectory review, regression analysis, release gates, production traces, or privacy-aware telemetry. Covers task and trajectory contracts, statistical comparisons, and incident-to-case learning; route framework implementation to pydanticai or langgraph when needed. - [agent-production-operations](bundles/agent-production-operations/SKILL.md): Operate an evaluated agent with tools and authority in production through a runtime control plane covering versioning, staged rollout, fallback, cost and latency budgets, tool health, human escalation, disablement, and trace-to-eval feedback. Do not use for building agents, designing evals, or general release, SRE, security, or platform engineering — those methods stay with their specialist skills. - [agent-skills](agent-skills/SKILL.md): Use this skill when creating, reviewing, or editing Agent Skills-format skills, or when implementing skill discovery and loading in an agent client. It covers directory structure, SKILL.md metadata, progressive disclosure, evals, and repository conventions. Do not use this skill for general software work that does not involve the Agent Skills format or lifecycle. +- [anydoc](anydoc/SKILL.md): Convert Word (.doc/.docx/.docm), PowerPoint (.ppt/.pps/.pot/.pptx/.pptm/.ppsx/.ppsm), Excel (.xls/.xlsx/.xlsm/.xlsb), OpenDocument (.odt/.ods/.odp), RTF, EPUB, CSV, and PDF documents to clean GitHub-Flavored Markdown locally with the Any Doc CLI (npx -y @firecrawl/anydoc@0.1.6): headings, GFM tables, slide structure, and footnotes in one pass. Use when a task needs the contents of an office document, spreadsheet, presentation, ebook, or PDF you cannot read directly. Do not use for generating, editing, or validating documents (use documents), for ebook packaging (use epub), or for OCR of scanned or image-only PDFs (anydoc does not OCR; route to OCR tooling). - [api-design-and-evolution](api-design-and-evolution/SKILL.md): Design, document, review, and evolve consumer-facing APIs and event interfaces. Use when choosing REST/HTTP, GraphQL, RPC, events, webhooks, or streaming; writing OpenAPI or AsyncAPI contracts; defining schemas, pagination, mutations, errors, idempotency, or API compatibility; or planning API versioning, deprecation, and migration. Use secure-software-engineering for a full security lifecycle, ADR authoring for durable architecture decisions, and spec-driven-development for a delivery specification and implementation gates. - [artifact-pyramids](artifact-pyramids/SKILL.md): Organize durable agent research outputs as summaries, analysis, and evidence dossiers. Use when producing multi-layer research artifacts or coordinating research handoffs. - [autogen](autogen/SKILL.md): Expert skill for conversational multi-agent AI with Microsoft AutoGen. AssistantAgent, UserProxyAgent, GroupChat, code execution, nested chats, cancellation tokens, tool integration, and MCP support. Use when building conversation-driven multi-agent systems or comparing agent frameworks.