{
  "markdown": "# refigure\n\n**Converters where figures survive.**\n\n[![CI](https://github.com/HelgDemidov/refigure/actions/workflows/ci.yml/badge.svg)](https://github.com/HelgDemidov/refigure/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/HelgDemidov/refigure/main/docs/assets/coverage-badge.json)](https://github.com/HelgDemidov/refigure/actions/workflows/ci.yml)\n[![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)\n[![PyPI](https://img.shields.io/pypi/v/refigure)](https://pypi.org/project/refigure/)\n[![Docker](https://img.shields.io/badge/ghcr.io-refigure-2496ED?logo=docker&logoColor=white)](https://github.com/HelgDemidov/refigure/pkgs/container/refigure)\n[![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6f42c1)](https://registry.modelcontextprotocol.io/?q=refigure)\n[![Claude Desktop](https://img.shields.io/badge/Claude_Desktop-.mcpb-D97757)](https://github.com/HelgDemidov/refigure/releases/latest/download/refigure.mcpb)\n[![AllMCPs Verified](https://allmcps.com/api/badge/refigure?style=shield)](https://allmcps.com/mcp/refigure)\n\n<!-- mcp-name: io.github.HelgDemidov/refigure -->\n\nDOCX/XLSX → Markdown that keeps charts and infographics machine-readable\ninstead of losing them to OCR or a vision model: native OOXML chart data\n(`numCache`/`strCache`) recovers exact numbers with zero GPU calls, zero\nVLM calls, zero lost precision — by default, not as a fallback.\n\nThat default path is also why the base install (`pip install\n\"refigure[docx,xlsx]\"`) is **~500x lighter than PyTorch-based\nalternatives** (5.6MB vs. multi-GB) — the core conversion needs no ML\nmodel at all. That number is about the core architecture, not every\ndistribution format: the Docker image trades it back deliberately,\nbundling VLM providers + LibreOffice for a turnkey composite-figure path\n(see Docker below).\n\nVLM interpretation itself is there for the rare figure with no native\ndata at all (a dashboard screenshot) — never required just to get real\nnumbers out of a chart, on any distribution format.\n\nShips as a library, CLI, MCP server, and a one-click Claude Desktop\nbundle — every surface returns the same native-fidelity output, not a\ndegraded summary for agents.\n\n## Features\n\n- **Native chart-data extraction** — reads OOXML `numCache`/`strCache`\n  directly; no rasterize/OCR/VLM step for charts, real numbers every time.\n- **Positioned zero-loss markers for composite figures** (DOCX) — grouped\n  shapes/infographics that mammoth would otherwise silently fragment into\n  disconnected pieces get a clean marker instead, with position and any\n  caption text preserved. Absent even in well-funded incumbents — see\n  [Docling issue #1287](https://github.com/docling-project/docling/issues/1287).\n- **Optional VLM interpretation** (DOCX composite figures, `[vlm]` extra,\n  `--vlm`/`Config(use_vlm=True)`) — cloud description + a real rendered\n  mermaid diagram (26 supported diagram types — flowcharts, pie/xy charts,\n  sequence/state/ER diagrams, Gantt/timeline/sankey/treemap and more, see\n  Status below) on top of the zero-loss floor, for figures with no native\n  chart data at all (e.g. a dashboard screenshot). Provider-agnostic —\n  OpenRouter by default, or direct OpenAI/Ollama/vLLM/LM Studio/Anthropic\n  via `--vlm-provider` (`[vlm-direct]` extra). `--strict` upgrades one\n  specific failure (the system `soffice`/LibreOffice binary missing) from\n  a graceful skip to a hard error; every other VLM failure still degrades.\n- **Rich, typed result** — `ConversionResult` (markdown + warnings +\n  chart/group counts + `vlm_used`), not a bare string.\n- **CLI included** — `refigure` console command, stdin/stdout-first, native\n  batch mode, typed exit codes (see below).\n- **MCP server included** — `refigure-mcp` console command (`[mcp]` extra),\n  stdio or Streamable HTTP, tools/resources/prompts, batch conversion with\n  per-file isolation (see below).\n- **Docker image** — `ghcr.io/helgdemidov/refigure`, both console commands\n  on `PATH`, `soffice`/LibreOffice baked in — the VLM composite-figure\n  path works turnkey, no manual LibreOffice install. Multi-arch —\n  `linux/amd64` + `linux/arm64`, native Apple Silicon (see below).\n- **`.mcpb` bundle for Claude Desktop** — one-click install, no terminal\n  (`docx`+`xlsx` only, see below).\n\n## Demo\n\n**Optional VLM interpretation** — for a figure with no native chart data at\nall (a screenshot, not an OOXML chart part) AND no matching mermaid\nconstruct either (a dense radial sunburst — nothing in the 4 original\nmermaid types could represent it), `--vlm` both recovers the real content\nand produces a genuinely renderable diagram, not just recovered text:\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/demo-vlm-dark.svg\">\n  <img src=\"docs/assets/demo-vlm-light.svg\" alt=\"A real docx image (a dense wireless-technology sunburst chart with no native chart data) converted by refigure.docx.convert(use_vlm=True) into a rich VLM-generated description and a real rendered mermaid mindmap diagram, laid out radially instead of the unreadable flat strip a generic flowchart construct would have produced\">\n</picture>\n\n**Native chart-data extraction** — real OOXML `numCache`, not a screenshot,\nnot OCR:\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/demo-dark.svg\">\n  <img src=\"docs/assets/demo-light.svg\" alt=\"A real xlsx bar chart converted by refigure.xlsx.convert() into Markdown, shown both as the raw text an LLM reads and as the same data re-rendered as a diagram\">\n</picture>\n\n**Same extraction, from DOCX** — Word embeds native charts too, not just\nExcel; refigure reads the same cached OOXML data either way:\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/demo-docx-chart-dark.svg\">\n  <img src=\"docs/assets/demo-docx-chart-light.svg\" alt=\"A real docx pie chart from an EU labour-platform survey converted by refigure.docx.convert() into Markdown, shown both as the raw text an LLM reads (mermaid fence + data table) and as the same data re-rendered as a diagram\">\n</picture>\n\n**Composite figures** — positioned, zero-loss, even when the figure itself\ncan't be rendered (no incumbent does this — see\n[Docling issue #1287](https://github.com/docling-project/docling/issues/1287),\nopen >1 year):\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/demo-groups-dark.svg\">\n  <img src=\"docs/assets/demo-groups-light.svg\" alt=\"A real docx composite figure (a grouped diagram refigure.docx.convert() can't render) converted into a positioned zero-loss marker that keeps the figure's own caption/legend text\">\n</picture>\n\n## Quickstart\n\n```bash\npip install \"refigure[docx,xlsx]\"\n```\n\n```bash\nrefigure report.docx                      # markdown to stdout\n```\n\n```python\nfrom refigure.docx import convert\n\nresult = convert(\"report.docx\")\nprint(result.markdown)\nprint(f\"{result.charts_found} charts, {result.groups_found} composite figures\")\n```\n\nOr without a permanent install, via [uv](https://docs.astral.sh/uv/)/`uvx`:\n\n```bash\nuvx --from \"refigure[docx,xlsx]\" refigure report.docx\n```\n\nOptional VLM interpretation, for a composite figure the chart engine can't\nreconstruct on its own (see Features above):\n\n```bash\npip install \"refigure[docx,vlm]\"\nexport OPENROUTER_API_KEY=...                 # or --vlm-api-key-file/--vlm-provider\nrefigure report.docx --vlm                    # needs the system soffice/LibreOffice binary too\n```\n\n## Installation & usage\n\nOne converter, four ways to run it — pick whichever fits your pipeline.\nClick a heading to expand it.\n\n<details>\n<summary><b>CLI</b> — a console command, stdin/stdout-first, native batch mode</summary>\n\n`refigure` installs a console command — a thin wrapper over the same\n`convert()` used programmatically, no separate logic:\n\n```bash\nrefigure report.docx                      # markdown to stdout\nrefigure report.docx -o report.md         # markdown to a file\ncat report.docx | refigure --format docx  # stdin, format hint required\nrefigure reports/ -o out/                 # batch: directory, walked recursively\nrefigure a.docx b.xlsx -o out/            # batch: 2+ explicit sources\n```\n\nBatch mode (2+ sources, or a single directory) requires `-o DIR`, keeps\ngoing past a failed source by default (`--fail-fast` aborts on the first\none instead), and always prints a summary (`N/M converted, K failed`) to\nstderr. `--json` emits the full result — markdown plus chart/group counts\nand warnings — instead of plain markdown. `-v`/`-q` control verbosity;\n`--strict` is forwarded to the same `Config.strict` the Python API uses.\n\nExit codes:\n\n| Code | Meaning |\n| --- | --- |\n| 0 | success |\n| 1 | batch mode: 1+ sources failed (keep-going default) |\n| 2 | usage error (bad arguments/flags) |\n| 3 | input isn't a valid document of its format |\n| 4 | input isn't a valid/safe archive |\n| 5 | the format's extra (`[docx]`/`[xlsx]`) isn't installed |\n| 6 | unexpected internal error |\n\n</details>\n\n<details>\n<summary><b>MCP server</b> — for agents/IDEs that speak the protocol directly</summary>\n\n`refigure-mcp` — the same converters as an\n[MCP](https://modelcontextprotocol.io) server, for agents/IDEs that speak\nthe protocol directly instead of shelling out to a CLI or importing the\nlibrary. Listed on the official\n[MCP Registry](https://registry.modelcontextprotocol.io/?q=refigure) as\n`io.github.HelgDemidov/refigure`:\n\n```bash\npip install \"refigure[mcp,docx,xlsx]\"\nrefigure-mcp                              # stdio — the MCP client launches it\n```\n\n```json\n{\n  \"mcpServers\": {\n    \"refigure\": { \"command\": \"refigure-mcp\" }\n  }\n}\n```\n\nOr point the client at `uvx` instead, with no permanent install at all:\n\n```json\n{\n  \"mcpServers\": {\n    \"refigure\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"refigure[mcp,docx,xlsx,vlm-direct]\", \"refigure-mcp\"]\n    }\n  }\n}\n```\n\n`refigure[full]` is a shortcut for `refigure[mcp,docx,xlsx,vlm-direct]` —\nevery tool, both formats, every VLM provider, one extras string.\n\nThree tools — `convert_docx`, `convert_xlsx`, and `convert_batch` (multiple\nfiles in one call: one bad file reports its own error without aborting the\nrest) — each registered only if its format extra is actually installed.\n`use_vlm`/`--vlm-provider` and friends work the same as the CLI. A result\ntoo large to inline is stored and handed back as a\n`refigure://conversion/{id}` resource instead of inflating the tool\nresponse. Two prompts (`ingest_for_rag`, `explain_conversion_warnings`)\nhelp a client pick the right tool/VLM settings for the job.\n\nStreamable HTTP is opt-in, for a shared/remote deployment — bearer-token\nauth is required, not optional:\n\n```bash\necho \"sk-... = alice\" > tokens.txt\nrefigure-mcp --transport http --mcp-auth-token-file tokens.txt\n```\n\nPer-caller rate-limiting (protects the operator's own spend from a\nleaked/runaway token) applies automatically over HTTP, together with a\nfairness soft-cap once 2+ callers are configured; `refigure-mcp --help`\ncovers every tuning flag (concurrency, timeouts, resource-store limits,\nbatch size, VLM ceiling).\n\n</details>\n\n<details>\n<summary><b>Docker</b> — one image, CLI and MCP server both on PATH, soffice baked in</summary>\n\nOne image, both surfaces — `refigure` and `refigure-mcp` are already on\n`PATH`, no separate CLI/MCP builds to choose between. The one thing this\nformat buys over `pip`/`uvx` that neither can: the system `soffice`/\nLibreOffice binary the VLM composite-figure path needs is baked in, not a\nmanual install. Multi-arch manifest (`linux/amd64` + `linux/arm64`) —\n`docker pull` resolves the right layer automatically, including on\nApple Silicon.\n\n```bash\ndocker pull ghcr.io/helgdemidov/refigure:latest\n```\n\nPin an exact version instead of `:latest` for reproducibility — e.g.\n`:0.3.4` — see the [package page](https://github.com/HelgDemidov/refigure/pkgs/container/refigure)\nfor available tags.\n\nThe package page's **OS/Arch** tab lists `unknown/unknown` alongside the\nreal `linux/amd64`/`linux/arm64` entries — that's a build-provenance/SBOM\nattestation (in-toto + SPDX metadata this image publishes for every\nplatform), not a broken or untrusted image. GHCR's own UI doesn't label\nattestation manifests, a\n[known, widely-reported limitation](https://github.com/docker/build-push-action/issues/900)\nof the registry's package view, unrelated to this project.\n\nCLI, via a bind mount (the image's working directory is already `/data`):\n\n```bash\ndocker run --rm -v \"$PWD:/data:ro\" ghcr.io/helgdemidov/refigure:latest \\\n  refigure /data/report.docx\n```\n\nMCP, stdio — the client launches the container itself:\n\n```json\n{\n  \"mcpServers\": {\n    \"refigure\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"ghcr.io/helgdemidov/refigure:latest\", \"refigure-mcp\"]\n    }\n  }\n}\n```\n\nMCP, Streamable HTTP — `--mcp-http-host 0.0.0.0` is required here, not\noptional: the default `127.0.0.1` bind is unreachable through `-p` port\npublishing (Docker's NAT reaches the container's external network\ninterface, not its loopback), so the \"obvious\" invocation without this\nflag would silently never respond:\n\n```bash\necho \"sk-... = alice\" > tokens.txt\ndocker run --rm -p 8000:8000 -v \"$PWD/tokens.txt:/data/tokens.txt:ro\" \\\n  ghcr.io/helgdemidov/refigure:latest \\\n  refigure-mcp --transport http --mcp-http-host 0.0.0.0 \\\n  --mcp-auth-token-file /data/tokens.txt\n```\n\n</details>\n\n<details>\n<summary><b>Claude Desktop (<code>.mcpb</code>)</b> — download, double-click, done</summary>\n\nThe simplest install for a non-technical user: download, double-click,\ndone — no terminal, no `pip`/`uvx`/`docker`. Covers `docx`+`xlsx`\nconversion only (no VLM — that needs the `[vlm]` extra, deliberately\nnot carried by this bundle); dependencies resolve fresh from PyPI via\n`uv` on first launch, the same mechanism `uvx` uses under the hood,\njust one click instead of a config snippet.\n\n[**Download refigure.mcpb**](https://github.com/HelgDemidov/refigure/releases/latest/download/refigure.mcpb)\n— open it with Claude Desktop to install.\n\n</details>\n\n## Real examples\n\nConcentrated excerpts (≤200 lines each) of real `convert()` output on\nreal, openly-licensed documents — the actual markdown a pipeline would\ningest, not a screenshot or a cherry-picked one-liner. Each file's own\nheader states its source, license and attribution; trimmed sections are\nmarked inline, never fabricated to fill space.\n\n| Source | Demonstrates | Output |\n| --- | --- | --- |\n| `hackair-d7.7-pilot-evaluation.docx` | native chart extraction — real survey tables + `xychart-beta` bar charts | [examples/hackair-native-charts.md](examples/hackair-native-charts.md) |\n| `swd2018-254-marine-litter-ia-annex.docx` | honest fallback — a chart that fails render-verification degrades to a clean table, plus 2 composite-figure zero-loss markers | [examples/swd2018-combo.md](examples/swd2018-combo.md) |\n| `govtech-2025-charts.xlsx` | XLSX native charts — 3 distinct types (`xychart-beta`/`radar-beta`/`pie`) from one workbook | [examples/govtech-xlsx-charts.md](examples/govtech-xlsx-charts.md) |\n| `swd2021-396-platform-work-ia.docx` | native pie + a 23-year time series, real EU-survey labels | [examples/swd2021-pie-chart.md](examples/swd2021-pie-chart.md) |\n| `efsa-trichinella-dashboard-guide.docx` | `--vlm` interpretation — 2 screenshot figures recovered as a bar chart and a UI flowchart, real numbers | [examples/efsa-trichinella-vlm.md](examples/efsa-trichinella-vlm.md) |\n\nOpen any of these on GitHub and both views are right there: the raw\n```` ```mermaid ```` fence an LLM/RAG pipeline would read, and its native\nGitHub rendering — no extra step, that's GitHub's own Markdown support.\n\n## Status\n\n- **Validated** against 27 real documents (15 DOCX + 12 XLSX) — 407 native\n  charts found (400 rendered), 35 composite figures recovered as\n  positioned zero-loss markers. Full provenance:\n  [`tests/integration/fixtures/manifest.yaml`](tests/integration/fixtures/manifest.yaml).\n- **Tested**: CI gates on a combined unit+integration coverage floor of 95%.\n- **Published** as `v0.3.4` — [PyPI](https://pypi.org/project/refigure/)\n  (trusted publishing, no stored tokens),\n  [GHCR](https://github.com/HelgDemidov/refigure/pkgs/container/refigure),\n  and the official\n  [MCP Registry](https://registry.modelcontextprotocol.io/?q=refigure) as\n  `io.github.HelgDemidov/refigure`. `refigure-md` is a reserved alternate\n  name, not an active release.\n\nExtracted from a working document-analysis pipeline (a government\nAI-policy research corpus), not built from scratch for this release.\n\nVLM interpretation of composite figures the chart engine can't reconstruct\nis fully implemented and tested, not a stub — `[vlm]` extra,\nprovider-agnostic (direct OpenAI/Anthropic via `[vlm-direct]`), also needs\nthe system `soffice`/LibreOffice binary.\n\nMermaid-diagram recognition depends on diagram type and on what the\nsource figure actually contains:\n\n- Common types (flowcharts, pie/xy charts) are picked reliably.\n- More specialized ones need an unambiguous visual cue on the source figure.\n- Not every figure produces a diagram — a plain-text description is an\n  honest fallback, not a failure.\n\n**PDF is out of scope, on purpose — a boundary, not a gap.** PDF has no\nequivalent of OOXML's cached chart data (`numCache`/`strCache`) for any\nmainstream chart generator, so the native, rasterize-free extraction this\nproject is built on doesn't transfer to it — confirmed by research into\nPDF's own structure and how leading PDF converters handle charts today,\nnot assumed. For mixed-format corpora, route by extension instead of\nexpecting one tool to cover everything:\n\n```python\nimport refigure.docx\nimport refigure.xlsx\n\nif path.suffix == \".pdf\":\n    markdown = docling_convert(path)      # or any PDF-capable converter\nelif path.suffix == \".docx\":\n    markdown = refigure.docx.convert(path).markdown\nelse:\n    markdown = refigure.xlsx.convert(path).markdown\n```\n\nUse [Docling](https://github.com/docling-project/docling) or\n[MarkItDown](https://github.com/microsoft/markitdown) for PDF, refigure\nfor DOCX/XLSX where the chart data actually survives in the file.\n\n## License\n\nApache-2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).\n",
  "bytes": 18306,
  "sha": "38cffb89a1d028325d9900cceeb3ee6cb23e2817741609c680394b43ffe25060",
  "repo_slug": "helgdemidov/refigure",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_helgdemidov_refigure_c8ff0ca6/readme"
}