From bb57268a68a3b598e940437532905d788cd769d2 Mon Sep 17 00:00:00 2001 From: Magnus Hedemark Date: Fri, 28 Aug 2026 15:33:48 -0400 Subject: [PATCH] feat(anydoc): support explicit hosted OCR (#420) * feat(anydoc): support explicit hosted OCR Closes #419 Signed-off-by: Magnus Hedemark * test(anydoc): update release contract expectations Signed-off-by: Magnus Hedemark * test(anydoc): align hosted OCR hint contract Signed-off-by: Magnus Hedemark * chore: refresh generated marketplace Signed-off-by: Magnus Hedemark * chore: refresh generated llms catalog Signed-off-by: Magnus Hedemark --------- Signed-off-by: Magnus Hedemark --- .claude-plugin/marketplace.json | 2 +- anydoc/README.md | 11 +- anydoc/SKILL.md | 60 ++++--- anydoc/references/cli-reference.md | 36 ++-- anydoc/references/errors.md | 24 ++- anydoc/references/formats.md | 17 +- anydoc/references/sources.md | 12 +- anydoc/references/workflows.md | 43 +++-- anydoc/scripts/anydoc | 156 +++++++++++++++--- anydoc/scripts/test_anydoc_hosted_ocr.py | 199 +++++++++++++++++++++++ anydoc/tests/test_anydoc.py | 12 +- llms.txt | 2 +- 12 files changed, 463 insertions(+), 111 deletions(-) create mode 100644 anydoc/scripts/test_anydoc_hosted_ocr.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ff58f0c..c7cc8db 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -84,7 +84,7 @@ "./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)." + "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.2.4): 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). For scanned or image-only PDFs, use hosted OCR only when the user explicitly authorizes whole-document upload; otherwise route to local OCR tooling." }, { "name": "api-design-and-evolution", diff --git a/anydoc/README.md b/anydoc/README.md index 9265a07..a1e7f7b 100644 --- a/anydoc/README.md +++ b/anydoc/README.md @@ -1,12 +1,12 @@ # 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. +Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF files into clean, LLM-friendly GitHub-Flavored Markdown. Local conversion stays on your machine; an explicitly authorized hosted OCR mode handles scanned PDFs through Firecrawl Parse when whole-document upload is acceptable. ## 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. +The skill wraps the pinned `@firecrawl/anydoc` v0.2.4 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, JSON output, and an explicit `--allow-hosted-upload` acknowledgement for hosted OCR. ## What You Get @@ -25,13 +25,13 @@ You need Node.js 20+ and `npx` (no other install — the CLI and its native bina ```bash cd anydoc -npx -y @firecrawl/anydoc@0.1.6 fixtures/fixture-handmade-outline.docx +npx -y @firecrawl/anydoc@0.2.4 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 +npx -y @firecrawl/anydoc@0.2.4 fixtures/fixture-handmade-outline.docx -o outline.md ``` Or use the wrapper for the same job: @@ -50,6 +50,7 @@ Load this skill when the task involves any of these: - "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) +- "OCR this scanned PDF" — use local OCR by default, or explicitly authorize `--ocr hosted --allow-hosted-upload` when sending the whole document to Firecrawl Parse is acceptable 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). @@ -58,4 +59,4 @@ Do **not** load this skill for document generation or editing ("create a docx re - **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. +- **Local mode needs no API key or service**. Hosted OCR uses Firecrawl Parse and may use `FIRECRAWL_API_KEY`; it sends the whole OCR-required PDF and has no page selection. diff --git a/anydoc/SKILL.md b/anydoc/SKILL.md index 4425516..d7d6928 100644 --- a/anydoc/SKILL.md +++ b/anydoc/SKILL.md @@ -4,19 +4,19 @@ 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 + (npx -y @firecrawl/anydoc@0.2.4): 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). + (use epub). For scanned or image-only PDFs, use hosted OCR only when the user + explicitly authorizes whole-document upload; otherwise route to local OCR tooling. license: MIT compatibility: >- - Node.js >= 20 and npx. The pinned CLI is @firecrawl/anydoc@0.1.6; the native + Node.js >= 20 and npx. The pinned CLI is @firecrawl/anydoc@0.2.4; 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. + compilation). Local conversion needs no service or API key. Hosted OCR sends the + whole PDF to Firecrawl Parse and may use FIRECRAWL_API_KEY. 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 @@ -28,9 +28,9 @@ allowed-tools: Bash Read 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. +Any Doc CLI (`@firecrawl/anydoc` v0.2.4). One shared document model and one GFM +serializer produce the same logical output across formats. Local conversion runs +without a service, API key, or file upload; hosted OCR is a separate explicit route. ## Overview @@ -51,11 +51,11 @@ Before invoking anydoc, classify the request: | If the user needs... | Do this | | --- | --- | | The contents of an existing supported document | Continue to [Command Map](#command-map). | -| Generation, editing, validation, EPUB packaging, HTML scraping, OCR, or password decryption | Stop and use the route in [When not to use](#when-not-to-use). | +| Generation, editing, validation, EPUB packaging, HTML scraping, or password decryption | Stop and use the route in [When not to use](#when-not-to-use). | | A format-fidelity or failure decision | Load the matching row in [Reference Routing](#reference-routing) before choosing a command. | | A conversion result | Choose stdout, `-o`, or batch; run it; then follow [Verification](#verification). | -> **Hard boundary:** anydoc reads existing supported documents to Markdown. It does not create, edit, validate, package, OCR, decrypt, or scrape them. +> **Hard boundary:** local anydoc conversion reads existing supported documents to Markdown without uploading them. Hosted OCR is opt-in only: it sends the whole OCR-required PDF to the configured Parse service. AnyDoc does not create, edit, validate, package, decrypt, or scrape documents. ## When to use @@ -85,7 +85,7 @@ epub, xlsx, ods, odp, csv`; extension aliases map through them (`.docm`→docx, | Rich Text Format | `.rtf` | same document shape as DOCX/ODT | Use for text extraction, not layout preservation. | | EPUB | `.epub` | `#` chapter headings, GFM tables, internal anchor links | Use to read an existing EPUB; use `epub` to author or package one. | | CSV | `.csv` | one GFM table; label-like first row promoted to header; delimiter sniffing; UTF-16 with BOM | Use for delimited tabular content; inspect delimiter and encoding when output looks wrong. | -| 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 | Use only for text-based PDFs; route scanned PDFs to OCR and treat tables as lower fidelity. | +| PDF | `.pdf` | headings + inline emphasis, but a lower-fidelity pipeline: tables flatten to text, footnotes and links degrade. Text-based PDFs stay local; scanned/image-only PDFs require explicit hosted OCR or another OCR tool | Use local mode by default; hosted mode uploads the whole PDF and has no page selection. | See [references/formats.md](references/formats.md) for the full per-format expectations and fidelity caveats, and [references/errors.md](references/errors.md) @@ -103,22 +103,44 @@ the document from stdin. | Convert one file to a markdown file | `anydoc/scripts/anydoc convert -o out.md` | The output is large, must be reviewed later, or should be preserved as an artifact. | | Convert many files to a directory | `anydoc/scripts/anydoc batch ... --out-dir out/` | The request is a bounded batch and per-file output/status is useful. | | Show the tool and pinned CLI version | `anydoc/scripts/anydoc info` | You need to confirm the executable and version before troubleshooting or reporting an environment issue. | -| Raw pinned CLI, one document | `npx -y @firecrawl/anydoc@0.1.6 [-o out.md]` | The wrapper is unavailable; preserve the pinned CLI and its documented semantics. | -| Raw pinned CLI, read stdin | `cat data.csv \| npx -y @firecrawl/anydoc@0.1.6 - --format csv` | Bytes already arrive on stdin and the format is known; keep the producer pipeline separate from the converter. | +| Raw pinned CLI, one document | `npx -y @firecrawl/anydoc@0.2.4 [-o out.md]` | The wrapper is unavailable; preserve the pinned CLI and its documented semantics. | +| Raw pinned CLI, read stdin | `cat data.csv \| npx -y @firecrawl/anydoc@0.2.4 - --format csv` | Bytes already arrive on stdin and the format is known; keep the producer pipeline separate from the converter. | + +For an OCR-required PDF, first use the local default so the failure is visible: + +```bash +anydoc/scripts/anydoc convert scan.pdf --ocr reject +``` + +If the user explicitly authorizes sending the complete PDF to Firecrawl Parse, +use the wrapper acknowledgement and a trusted `FIRECRAWL_API_KEY` environment +variable when needed: + +```bash +anydoc/scripts/anydoc convert scan.pdf --ocr hosted --allow-hosted-upload +``` + +The wrapper never places the key on the command line. Hosted OCR has no page +selection, and a hosted failure is not permission to silently switch endpoints. 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 +- The raw `npx -y @firecrawl/anydoc@0.2.4` 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 +- Always pin `@0.2.4` 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. +Hosted OCR is supported by the 0.2.4 library and CLI, but the wrapper requires +both `--ocr hosted` and `--allow-hosted-upload` so an upload cannot be selected +implicitly. The hosted route sends the complete PDF to Firecrawl Parse because +page selection is unavailable. Do not place API keys on the command line. + ## Reference Routing Load only the row that answers the immediate question; the command examples and verification contract remain in this file. @@ -140,7 +162,7 @@ Use this routing table before reaching for a conversion command: | --- | --- | --- | | Generate, edit, inspect rendered layout, or validate a PDF/Word/Excel/PowerPoint artifact | `documents` skill | anydoc extracts existing document contents to Markdown; it does not author, preserve rendered layout, or validate artifacts. | | Package or author an EPUB | `epub` skill | anydoc reads an existing EPUB to Markdown but never writes or validates an EPUB container. | -| OCR a scanned or image-only PDF | OCR tooling or the hosted Firecrawl Parse API | anydoc has no OCR path; report the documented unsupported error and do not retry locally. | +| OCR a scanned or image-only PDF | Local OCR tooling, or AnyDoc hosted OCR after explicit authorization | Local mode reports the OCR-required error without uploading; hosted mode sends the whole PDF to Firecrawl Parse. | | Scrape HTML or other web content | A web-scraping skill | HTML is not a supported anydoc input. | | Transcribe binary media such as images, video, or audio | A media or transcription tool | Embedded images become alt text; anydoc cannot transcribe media. | | Preserve pagination, fonts, templates, or rendered layout | A document/layout tool | The only output contract is GitHub-Flavored Markdown. | @@ -164,7 +186,7 @@ Caveat/route: | Condition | Do not | Next | | --- | --- | --- | -| Scanned or image-only PDF / OCR-required error | Retry the same file locally | Route to OCR tooling or the hosted Firecrawl Parse API. | +| Scanned or image-only PDF / OCR-required error | Retry unchanged or upload implicitly | Use local OCR, or explicitly authorize and run `--ocr hosted --allow-hosted-upload`; page selection is unavailable. | | Encrypted or password-protected document | Guess a password or retry unchanged | Request an unencrypted copy or owner-authorized re-export. | | Unsupported, malformed, or resource-limit error | Guess a parser or claim partial success | Match the exact error in [references/errors.md](references/errors.md) and follow its bounded route. | | Exit 0 but expected structural markers are absent | Report success from the exit code alone | Inspect the output shape and source fidelity before reporting completion. | diff --git a/anydoc/references/cli-reference.md b/anydoc/references/cli-reference.md index 9a27050..3b7b073 100644 --- a/anydoc/references/cli-reference.md +++ b/anydoc/references/cli-reference.md @@ -1,7 +1,7 @@ -# CLI reference: the Any Doc CLI (pinned @firecrawl/anydoc@0.1.6) +# CLI reference: the Any Doc CLI (pinned @firecrawl/anydoc@0.2.4) 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 +(`npx -y @firecrawl/anydoc@0.2.4`, version 0.2.4, 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. @@ -24,6 +24,8 @@ Options: doc, docx, odt, pdf, ppt, pptx, rtf, epub, xlsx, ods, odp, csv (extension aliases like xls, docm, ppsx resolve to these) + --ocr `reject` (default) or `hosted` for OCR-required PDFs + --allow-hosted-upload Acknowledges whole-document upload for hosted OCR -h, --help Print this help and exit -V, --version Print the version and exit @@ -44,7 +46,7 @@ Examples: curl -s https://example.com/paper.pdf | anydoc - ``` -`anydoc --version` prints exactly `0.1.6` (verified; both `--help` and +`anydoc --version` prints exactly `0.2.4` (verified; both `--help` and `--version` exit 0 and write to stdout). ## Invocation forms @@ -70,7 +72,7 @@ anydoc - [options] < file # read the document from stdin | `-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`. | +| `-V`, `--version` | Print the version (`0.2.4`) 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: