|
|
|
@@ -0,0 +1,126 @@
|
|
|
|
|
---
|
|
|
|
|
name: document-to-markdown
|
|
|
|
|
surfaces: [claude-code, library]
|
|
|
|
|
summary: "Read .docx/.xlsx/.pptx/.epub/.msg and other formats the Read tool cannot open"
|
|
|
|
|
use_when: "You need the contents of a binary document — Word, Excel, PowerPoint, EPUB, Outlook .msg, a zip of documents — and Read returns nothing useful"
|
|
|
|
|
description: Convert a binary document to Markdown locally with markitdown. No network, no LLM, safe for confidential material. Says when to use the docmark service instead.
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Document → Markdown
|
|
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
|
|
`Read` opens PDFs, images and notebooks natively. It does **not** open `.docx`,
|
|
|
|
|
`.xlsx`, `.pptx`, `.epub`, `.msg` or `.zip`. This skill converts those locally.
|
|
|
|
|
|
|
|
|
|
One command, no install step, no service:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
uvx --from 'markitdown[docx,pdf,pptx,xls,xlsx,outlook]' markitdown FILE > /tmp/out.md
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
First run downloads 38 packages (~10s); afterwards it is cached and instant.
|
|
|
|
|
|
|
|
|
|
## When to use this
|
|
|
|
|
|
|
|
|
|
You have a file on disk whose contents you need. That is the whole trigger.
|
|
|
|
|
|
|
|
|
|
## When NOT to use this — use `docmark` instead
|
|
|
|
|
|
|
|
|
|
Three cases, and only three:
|
|
|
|
|
|
|
|
|
|
1. **A scanned PDF or an image whose content is pixels, not text.** Extracting
|
|
|
|
|
that needs a vision model. The estate's only audited path from an image to a
|
|
|
|
|
model is `docmark`, whose `sovereignty.py` constructs an LLM client *only*
|
|
|
|
|
when `DOCMARK_VISION_MODEL` matches `koala/*` or `iguana/*`, and silently
|
|
|
|
|
degrades to metadata-only otherwise. `tests/test_sovereignty.py` is the
|
|
|
|
|
load-bearing guard. Do not reach for a cloud vision model directly; that is
|
|
|
|
|
the boundary the gate exists to hold.
|
|
|
|
|
2. **You are writing an in-cluster service** (something in `dispatch`,
|
|
|
|
|
`ingestion`, `cobalt-dingo`). Call `docmark` over MCP rather than bundling a
|
|
|
|
|
converter into each service. Its NetworkPolicy already admits `supervisor`
|
|
|
|
|
and `dispatch`.
|
|
|
|
|
3. **The file is larger than ~25 MiB** or you want the input/output caps
|
|
|
|
|
enforced centrally.
|
|
|
|
|
|
|
|
|
|
For a text-bearing document, the service returns **byte-identical output** to
|
|
|
|
|
the local command — measured 2026-09-24 on the same `.docx`, tables included.
|
|
|
|
|
A network hop, a bearer token and an ingress for the same bytes is not a
|
|
|
|
|
trade worth making.
|
|
|
|
|
|
|
|
|
|
## Confidential material
|
|
|
|
|
|
|
|
|
|
**Conversion is local and involves no LLM.** `markitdown` uses an LLM client
|
|
|
|
|
only to describe images; with none configured it does not call anything. No
|
|
|
|
|
bytes leave the machine.
|
|
|
|
|
|
|
|
|
|
That makes this safe for client material, unlike anything that routes through a
|
|
|
|
|
model. Two cautions:
|
|
|
|
|
|
|
|
|
|
- **Do not install `markitdown[all]`.** It pulls `az-doc-intel` and
|
|
|
|
|
`az-content-understanding` — Azure cloud document-intelligence converters.
|
|
|
|
|
They need explicit endpoint configuration to activate, but the extras list
|
|
|
|
|
above avoids having them on the path at all.
|
|
|
|
|
- The output is a file like any other. If the source was confidential, so is
|
|
|
|
|
`/tmp/out.md` — and the usual rule applies: it must not reach an open-egress
|
|
|
|
|
sink (ADR-0014).
|
|
|
|
|
|
|
|
|
|
## Verify the conversion, do not assume it
|
|
|
|
|
|
|
|
|
|
The failure mode is **a successful exit with empty output**. A `.docx` template
|
|
|
|
|
with no body content converts to zero characters and exits 0; so does a scanned
|
|
|
|
|
PDF with no text layer. Both look like success.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
uvx --from 'markitdown[docx,pdf,pptx,xls,xlsx,outlook]' markitdown FILE > /tmp/out.md
|
|
|
|
|
wc -c /tmp/out.md
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If the byte count is 0 or implausibly small for the document's size, the
|
|
|
|
|
converter found no text layer — that is case 1 above, and it needs `docmark`
|
|
|
|
|
with a vision model, not a retry.
|
|
|
|
|
|
|
|
|
|
## What it converts
|
|
|
|
|
|
|
|
|
|
18 converters with the extras above:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Audio, BingSerp, Csv, Docx, Epub, Html, Image, Ipynb, OutlookMsg, Pdf,
|
|
|
|
|
PlainText, Pptx, Rss, Wikipedia, Xls, Xlsx, YouTube, Zip
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`docmark` registers 22 — the same set plus `DocxWithOCR`, `PdfWithOCR`,
|
|
|
|
|
`PptxWithOCR`, `XlsxWithOCR`. **Those four are the entire difference**, and
|
|
|
|
|
they are exactly the vision path from case 1. Everything else is identical code
|
|
|
|
|
running in a different place.
|
|
|
|
|
|
|
|
|
|
## Reading the output
|
|
|
|
|
|
|
|
|
|
Convert to a file and `Read` it, rather than piping into the transcript —
|
|
|
|
|
a 200-page document produces a lot of Markdown and most of it will not be
|
|
|
|
|
what you needed. Convert, then grep or read the section you want.
|
|
|
|
|
|
|
|
|
|
## Provenance
|
|
|
|
|
|
|
|
|
|
`docmark` ADR-0013 (`mathias/infra docs/decisions/`) owns the service; this
|
|
|
|
|
skill is the local path. The split was decided in `mathias/docmark#3` on
|
|
|
|
|
measurement, not preference: three of docmark's four plausible consumers are
|
|
|
|
|
better served without it, and the fourth — vision under a sovereignty gate — is
|
|
|
|
|
why it exists.
|
|
|
|
|
|
|
|
|
|
## Escalation
|
|
|
|
|
|
|
|
|
|
Local, read-only, single command. Nothing here is handed back mid-task.
|
|
|
|
|
|
|
|
|
|
Stop and hand back when:
|
|
|
|
|
|
|
|
|
|
- **The output is empty or near-empty.** Do not retry, and do not guess at the
|
|
|
|
|
contents. Say which file, what the byte count was, and that it needs a vision
|
|
|
|
|
path — then either use `docmark` with a local vision model, or ask. A
|
|
|
|
|
confident summary of a document you could not read is the worst possible
|
|
|
|
|
output of this skill.
|
|
|
|
|
- **The document is client material and the task would send its contents
|
|
|
|
|
anywhere** — a commit message, an issue, a PR body, a cloud model. That is
|
|
|
|
|
ADR-0014's refusal, and it is not this skill's call to make.
|
|
|
|
|
- **The file is encrypted or password-protected.** markitdown will fail; asking
|
|
|
|
|
for the password is a human's decision, not a retry.
|