tabella-docs 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tabella/read.py ADDED
@@ -0,0 +1,248 @@
1
+ """documents.read — read documents via anydoc (primary) + LiteParse (OCR fallback).
2
+
3
+ Merged from the doc_read plugin. Dependencies (firecrawl-anydoc, liteparse) are
4
+ now installed directly in the main Hermes venv — no subprocess/venv indirection.
5
+
6
+ Architecture:
7
+ - Office formats (DOCX, DOC, XLSX, XLSB, PPTX, PPT, ODT, ODS, ODP, RTF,
8
+ EPUB, CSV) and text-based PDFs → anydoc (Rust, in-process, ~4ms,
9
+ GitHub-Flavored Markdown output).
10
+ - Scanned PDFs and images (JPG, PNG, TIFF) → LiteParse (Tesseract OCR).
11
+ Used when anydoc returns empty for a PDF, or for image files directly.
12
+ - Plain text (.txt, .md, .json, .py, .yaml, etc.) → direct file read.
13
+ """
14
+ import json
15
+ from pathlib import Path
16
+ from typing import Any, Dict, Optional
17
+
18
+ # ── Format classification ────────────────────────────────────────────────────
19
+
20
+ TEXT_SUFFIXES = {
21
+ ".txt", ".md", ".markdown", ".json", ".html", ".htm",
22
+ ".xml", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf",
23
+ ".rst", ".fountain", ".log", ".py", ".js", ".ts", ".css",
24
+ }
25
+
26
+ # Formats anydoc handles (office + text PDF).
27
+ # Source: anydoc README — supported formats table.
28
+ ANYDOC_SUFFIXES = {
29
+ ".doc", ".docx", ".docm",
30
+ ".ppt", ".pps", ".pot", ".pptx", ".pptm", ".ppsx", ".ppsm",
31
+ ".xls", ".xlsx", ".xlsm", ".xlsb",
32
+ ".odt", ".ods", ".odp",
33
+ ".rtf",
34
+ ".epub",
35
+ ".pdf",
36
+ ".csv",
37
+ }
38
+
39
+ # Image formats that need OCR (LiteParse + Tesseract).
40
+ IMAGE_SUFFIXES = {
41
+ ".jpg", ".jpeg", ".png", ".tif", ".tiff", ".webp", ".bmp", ".gif",
42
+ }
43
+
44
+ # ── anydoc (primary engine) ──────────────────────────────────────────────────
45
+
46
+ def _run_anydoc(path: str, timeout: int = 60) -> str:
47
+ """Convert a document to Markdown using anydoc.
48
+
49
+ Returns the Markdown text, or empty string if the document has no
50
+ extractable text (e.g. scanned PDF without OCR).
51
+ """
52
+ import anydoc
53
+ md = anydoc.to_markdown(path)
54
+ return md or ""
55
+
56
+
57
+ # ── LiteParse (OCR fallback) ─────────────────────────────────────────────────
58
+
59
+ def _run_liteparse(path: str, extract_images: bool = True, timeout: int = 120) -> Dict[str, Any]:
60
+ """Parse a document using LiteParse with OCR enabled.
61
+
62
+ Used for scanned PDFs and images where anydoc returns empty.
63
+ """
64
+ from liteparse import LiteParse, ParseResult
65
+
66
+ parser = LiteParse(ocr_enabled=extract_images, output_format="text")
67
+ result = parser.parse(path)
68
+ text = ""
69
+ if isinstance(result, ParseResult):
70
+ text = getattr(result, "text", None) or getattr(result, "markdown", None) or ""
71
+ elif isinstance(result, str):
72
+ text = result
73
+ else:
74
+ text = str(result)
75
+ return {
76
+ "text": text,
77
+ "metadata": getattr(result, "metadata", {}) if hasattr(result, "metadata") else {},
78
+ }
79
+
80
+
81
+ # ── Plain text fast path ─────────────────────────────────────────────────────
82
+
83
+ def _read_text(path: str, max_chars: Optional[int] = None) -> str:
84
+ encodings = ["utf-8", "utf-8-sig", "latin-1"]
85
+ last_err = None
86
+ for enc in encodings:
87
+ try:
88
+ with open(path, "r", encoding=enc, errors="replace") as f:
89
+ text = f.read()
90
+ if max_chars:
91
+ text = text[:max_chars]
92
+ return text
93
+ except Exception as e:
94
+ last_err = e
95
+ raise RuntimeError(f"Could not read text file: {last_err}")
96
+
97
+
98
+ # ── Main dispatch ────────────────────────────────────────────────────────────
99
+
100
+ def read(path: str, max_chars: Optional[int] = None, extract_images: bool = True, max_pages: Optional[int] = None) -> str:
101
+ """Extract text from a document.
102
+
103
+ Dispatch:
104
+ 1. Plain text → direct file read (fast path)
105
+ 2. Office formats + text PDFs → anydoc (Markdown output)
106
+ 3. Images → LiteParse (OCR)
107
+ 4. PDFs that anydoc returns empty → LiteParse (OCR fallback for scanned PDFs)
108
+ """
109
+ suffix = Path(path).suffix.lower()
110
+
111
+ # 1. Plain text fast path
112
+ if suffix in TEXT_SUFFIXES:
113
+ return _read_text(path, max_chars)
114
+
115
+ # 2. Images → LiteParse OCR directly
116
+ if suffix in IMAGE_SUFFIXES:
117
+ data = _run_liteparse(path, extract_images=extract_images)
118
+ text = data.get("text") or ""
119
+ if max_chars:
120
+ text = text[:max_chars]
121
+ return text
122
+
123
+ # 3. Office formats + PDFs → anydoc first
124
+ if suffix in ANYDOC_SUFFIXES or suffix not in TEXT_SUFFIXES:
125
+ text = ""
126
+ anydoc_error = None
127
+ try:
128
+ text = _run_anydoc(path)
129
+ except (RuntimeError, ImportError) as e:
130
+ anydoc_error = str(e)
131
+
132
+ # 4. If anydoc returned empty text (scanned PDF) or failed, try LiteParse OCR
133
+ if not text.strip():
134
+ try:
135
+ data = _run_liteparse(path, extract_images=extract_images)
136
+ text = data.get("text") or ""
137
+ except (RuntimeError, ImportError) as liteparse_err:
138
+ if anydoc_error:
139
+ raise RuntimeError(f"anydoc failed: {anydoc_error}; liteparse fallback also failed: {liteparse_err}")
140
+ raise RuntimeError(f"both anydoc and liteparse returned no text. liteparse: {liteparse_err}")
141
+
142
+ if max_chars:
143
+ text = text[:max_chars]
144
+ return text
145
+
146
+ # Fallback: unknown format, try plain text read
147
+ return _read_text(path, max_chars)
148
+
149
+
150
+ def read_meta(path: str) -> Dict[str, Any]:
151
+ """Extract metadata from a document."""
152
+ suffix = Path(path).suffix.lower()
153
+ if suffix in TEXT_SUFFIXES:
154
+ return {"path": path, "format": "text"}
155
+
156
+ # LiteParse has the richest metadata extraction (office + PDF + images).
157
+ try:
158
+ data = _run_liteparse(path, extract_images=False)
159
+ meta = data.get("metadata") or {}
160
+ meta["path"] = path
161
+ return meta
162
+ except Exception:
163
+ # LiteParse unavailable (e.g. its LibreOffice fallback missing for office
164
+ # formats). Fall back to filesystem-truth metadata rather than failing:
165
+ # callers get valid basics instead of a tool error.
166
+ import os
167
+
168
+ st = os.stat(path)
169
+ return {
170
+ "path": path,
171
+ "format": suffix.lstrip("."),
172
+ "size_bytes": st.st_size,
173
+ "mtime": st.st_mtime,
174
+ }
175
+
176
+
177
+ # ── Hermes plugin registration ──
178
+
179
+ def register(registry):
180
+ if registry is None:
181
+ return
182
+
183
+ def doc_read(path: str, max_chars: int = 1_000_000, extract_images: bool = True):
184
+ try:
185
+ text = read(path, max_chars=max_chars, extract_images=extract_images)
186
+ return {
187
+ "success": True,
188
+ "path": path,
189
+ "text": text,
190
+ "length": len(text),
191
+ "message": f"Read {len(text)} chars from {path}",
192
+ }
193
+ except Exception as e:
194
+ return {"success": False, "path": path, "error": f"{type(e).__name__}: {e}"}
195
+
196
+ def doc_read_meta(path: str):
197
+ try:
198
+ meta = read_meta(path)
199
+ return {"success": True, "path": path, "metadata": meta}
200
+ except Exception as e:
201
+ return {"success": False, "path": path, "error": f"{type(e).__name__}: {e}"}
202
+
203
+ registry.register_tool(
204
+ name="doc_read",
205
+ toolset="doc",
206
+ schema={
207
+ "type": "object",
208
+ "properties": {
209
+ "path": {
210
+ "type": "string",
211
+ "description": "Absolute path to the document.",
212
+ },
213
+ "max_chars": {
214
+ "type": "integer",
215
+ "description": "Maximum characters to return.",
216
+ "default": 1_000_000,
217
+ },
218
+ "extract_images": {
219
+ "type": "boolean",
220
+ "description": "Extract text from images via OCR (LiteParse + Tesseract).",
221
+ "default": True,
222
+ },
223
+ },
224
+ "required": ["path"],
225
+ },
226
+ handler=lambda args, **kw: json.dumps(doc_read(
227
+ args["path"],
228
+ max_chars=args.get("max_chars", 1_000_000),
229
+ extract_images=args.get("extract_images", True),
230
+ )),
231
+ description="Read and extract text from documents. Uses anydoc for office formats (DOCX, XLSX, PPTX, ODT, RTF, EPUB, PDF → Markdown) and LiteParse for OCR (scanned PDFs, images). Plain text files read directly.",
232
+ )
233
+ registry.register_tool(
234
+ name="doc_read_meta",
235
+ toolset="doc",
236
+ schema={
237
+ "type": "object",
238
+ "properties": {
239
+ "path": {
240
+ "type": "string",
241
+ "description": "Absolute path to the document.",
242
+ },
243
+ },
244
+ "required": ["path"],
245
+ },
246
+ handler=lambda args, **kw: json.dumps(doc_read_meta(args["path"])),
247
+ description="Read document metadata (PDF info, image EXIF, etc.) via LiteParse.",
248
+ )
tabella/utils.py ADDED
@@ -0,0 +1,43 @@
1
+ """Shared helpers for the documents plugin.
2
+
3
+ Consolidates output-directory resolution that was previously duplicated in
4
+ doc_convert and doc_create.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+
11
+ def _get_output_dir() -> Path:
12
+ """Resolve output directory for documents.
13
+
14
+ Uses DOCS_OUTPUT_DIR env var if set, otherwise defaults to ~/Documents.
15
+ Fleet pCloud routing is handled by the document-routing skill, not here.
16
+ """
17
+ d = os.environ.get("DOCS_OUTPUT_DIR")
18
+ if d:
19
+ p = Path(d).expanduser()
20
+ else:
21
+ p = Path.home() / "Documents"
22
+ p.mkdir(parents=True, exist_ok=True)
23
+ return p
24
+
25
+
26
+ def _is_in_output_dir(path: Path) -> bool:
27
+ """Check if path is under the configured output directory."""
28
+ try:
29
+ resolved = path.expanduser().resolve()
30
+ user_dir = _get_output_dir()
31
+ return resolved != user_dir and user_dir in resolved.parents
32
+ except Exception:
33
+ return False
34
+
35
+
36
+ def _resolve_output_dir(category: str = None) -> Path:
37
+ """Return output directory with optional category subfolder."""
38
+ base = _get_output_dir()
39
+ if category:
40
+ out = base / category
41
+ out.mkdir(parents=True, exist_ok=True)
42
+ return out
43
+ return base
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: tabella-docs
3
+ Version: 2.0.1
4
+ Summary: Portable document lifecycle suite — create (.docx/.xlsx/.pptx/.pdf/.odt/.ods/.odp), convert (pandoc), and read (anydoc + LiteParse OCR) documents. Agent Plugins v1 compatible.
5
+ Author-email: D Yiapanis <d@yiapanis.co>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dyiapanis/tabella
8
+ Project-URL: Source, https://github.com/dyiapanis/tabella
9
+ Project-URL: Issues, https://github.com/dyiapanis/tabella/issues
10
+ Keywords: documents,docx,xlsx,pptx,pdf,odt,pandoc,anydoc,document-generation,markdown,ocr,agent-plugins,mcp
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business :: Office Suites
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: create
23
+ Requires-Dist: python-docx<2.0,>=1.0; extra == "create"
24
+ Requires-Dist: openpyxl<4.0,>=3.1; extra == "create"
25
+ Requires-Dist: python-pptx<2.0,>=0.6; extra == "create"
26
+ Requires-Dist: weasyprint<70.0,>=60.0; extra == "create"
27
+ Requires-Dist: odfpy<2.0,>=1.4; extra == "create"
28
+ Requires-Dist: Pillow<13.0,>=10.0; extra == "create"
29
+ Requires-Dist: lxml<7.0,>=5.0; extra == "create"
30
+ Provides-Extra: read
31
+ Requires-Dist: firecrawl-anydoc<0.3.0,>=0.1.1; extra == "read"
32
+ Requires-Dist: liteparse<3.0.0,>=2.0.0; extra == "read"
33
+ Requires-Dist: PyMuPDF<2.0,>=1.24; extra == "read"
34
+ Provides-Extra: convert
35
+ Provides-Extra: all
36
+ Requires-Dist: python-docx<2.0,>=1.0; extra == "all"
37
+ Requires-Dist: openpyxl<4.0,>=3.1; extra == "all"
38
+ Requires-Dist: python-pptx<2.0,>=0.6; extra == "all"
39
+ Requires-Dist: weasyprint<70.0,>=60.0; extra == "all"
40
+ Requires-Dist: odfpy<2.0,>=1.4; extra == "all"
41
+ Requires-Dist: Pillow<13.0,>=10.0; extra == "all"
42
+ Requires-Dist: lxml<7.0,>=5.0; extra == "all"
43
+ Requires-Dist: firecrawl-anydoc<0.3.0,>=0.1.1; extra == "all"
44
+ Requires-Dist: liteparse<3.0.0,>=2.0.0; extra == "all"
45
+ Requires-Dist: PyMuPDF<2.0,>=1.24; extra == "all"
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest>=7.0; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # tabella
51
+
52
+ A portable document lifecycle suite: create, convert, and read documents
53
+ from structured JSON or raw HTML/CSS.
54
+
55
+ ## Cross-Platform Compatibility
56
+
57
+ This package conforms to the [Agent Plugins v1.0.0](https://agent-plugins.org/specification)
58
+ specification. It works with any compatible AI agent client that supports
59
+ MCP servers, including:
60
+
61
+ - [Hermes Agent](https://hermes-agent.nousresearch.com)
62
+ - [Cursor](https://cursor.com)
63
+ - [Claude Desktop](https://claude.ai/download)
64
+ - Any client that supports the Model Context Protocol
65
+
66
+ ### Three installation paths
67
+
68
+ **Transports:** the plugin ships both.
69
+
70
+ - **stdio (default)** — the declaring client spawns `mcp_server.py` itself
71
+ (Agent Plugins v1 lifecycle; `PLUGIN_ROOT`/`PLUGIN_DATA` injected by the
72
+ client). Nothing to run by hand.
73
+ - **streamable-HTTP (self-host lane)** — for clients that prefer connecting
74
+ to a URL, run the server yourself:
75
+
76
+ ```bash
77
+ python3 scripts/mcp_http_server.py # serves 127.0.0.1:8602
78
+ ```
79
+
80
+ First run bootstraps the dependency venv (or `scripts/setup.sh`);
81
+ `TABELLA_TOKEN` enables bearer auth. Then point an `mcp.json`
82
+ `streamable-http` entry at `http://127.0.0.1:8602/mcp`.
83
+
84
+
85
+
86
+ **Agent Plugins v1 package** — clients that support the v1 spec auto-discover
87
+ `plugin.json` + `mcp.json` from the repo root and launch the MCP server:
88
+
89
+ ```bash
90
+ # Hermes Agent
91
+ hermes plugins install dyiapanis/tabella --no-enable
92
+ hermes plugins enable tabella
93
+
94
+ # Other v1-compatible clients: follow your client's plugin installation instructions
95
+ ```
96
+
97
+ **MCP server (manual)** — point any MCP-compatible client at the server directly:
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "tabella": {
103
+ "type": "stdio",
104
+ "command": "python3",
105
+ "args": ["/path/to/tabella/mcp_server.py"]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ **pip install** — use the Python library directly:
112
+
113
+ ```bash
114
+ pip install tabella-docs[all]
115
+ ```
116
+
117
+ Or install only the modules you need:
118
+
119
+ ```bash
120
+ pip install tabella-docs[create] # python-docx, openpyxl, python-pptx, weasyprint, odfpy
121
+ pip install tabella-docs[read] # firecrawl-anydoc, liteparse, PyMuPDF
122
+ pip install tabella-docs[convert] # no python deps (needs system pandoc + texlive-xetex)
123
+ ```
124
+
125
+ ## Modules
126
+
127
+ ### Create
128
+
129
+ Generate documents from structured JSON content or raw HTML/CSS:
130
+
131
+ - **DOCX** — Word documents (python-docx)
132
+ - **XLSX** — Spreadsheets (openpyxl)
133
+ - **PPTX** — Presentations (python-pptx)
134
+ - **PDF** — Print-ready documents and visual designs (WeasyPrint)
135
+ - Document-style: structured JSON → HTML → PDF
136
+ - Visual-style: raw HTML/CSS (Grid, Flexbox, gradients, themes) → PDF
137
+ - **ODT/ODS/ODP** — OpenDocument formats (odfpy)
138
+
139
+ Accepts plain strings (auto-normalized), HTML, or structured dicts with
140
+ sections, sheets, slides, tables, images, and page breaks.
141
+
142
+ ### Convert
143
+
144
+ Format conversion via pandoc + LaTeX (page size, margins, headers/footers
145
+ configurable). **Pandoc 3.1 supports 44 input and 54 output formats** —
146
+ `doc_convert` passes your `from`/`to` through, so all of them work. The
147
+ everyday set:
148
+
149
+ | Format | In | Out | Notes |
150
+ |---|---|---|---|
151
+ | Markdown (`.md`, variants) | ✅ | ✅ | gfm, commonmark, multimarkdown, strict… |
152
+ | HTML | ✅ | ✅ | html4/html5, chunkedhtml |
153
+ | DOCX (Word) | ✅ | ✅ | |
154
+ | ODT | ✅ | ✅ | + `opendocument` |
155
+ | PDF | ✅ | ✅ | in: text-based PDFs (pandoc's pdf reader); out: needs `pdflatex`/`xelatex` (texlive) — engine auto-selected |
156
+ | LaTeX (`.tex`) | ✅ | ✅ | incl. beamer slides out |
157
+ | RST | ✅ | ✅ | |
158
+ | EPUB | ✅ | ✅ | out: epub2/epub3 |
159
+ | CSV / TSV | ✅ | ✅ | out via markdown/AST route |
160
+ | RTF | ✅ | ✅ | |
161
+ | Plain text (`.txt`) | ✅ | ✅ | |
162
+ | Org-mode, MediaWiki, Jira wiki, Textile, DocBook, FB2, OPML, IPynb, JATS | ✅ | ✅ | writers for most |
163
+ | Typst / man / ms / texinfo / AsciiDoc | ✅ | ✅ | typst in+out |
164
+ | Presentations | — | ✅ | beamer, revealjs, slidy, s5, dzslides (from any reader, Markdown especially) |
165
+ | Pandoc AST (JSON) | ✅ | ✅ | for programmatic pipelines |
166
+
167
+ Everything not in the table: if `pandoc --list-input-formats` /
168
+ `--list-output-formats` lists it, `doc_convert` accepts it as-is — unknown
169
+ extensions pass through untouched.
170
+
171
+ ### Read
172
+
173
+ Extract text and metadata from any document:
174
+
175
+ - **Office formats** (DOCX, DOC, XLSX, XLSB, PPTX, PPT, ODT, ODS, ODP, RTF,
176
+ EPUB, CSV) and **text-based PDFs** → GitHub-Flavored Markdown via
177
+ [anydoc](https://github.com/firecrawl/anydoc) (~4ms, in-process)
178
+ - **Scanned PDFs** and **images** (JPG, PNG, TIFF) → OCR via
179
+ [LiteParse](https://github.com/run-llama/liteparse) (Tesseract)
180
+ - **Plain text** (.txt, .md, .json, .py, .yaml, etc.) → direct file read
181
+ - Returns structured Markdown (office formats) or plain text (OCR, plain text)
182
+
183
+ ## Prerequisites
184
+
185
+ ### Create module
186
+
187
+ The create module uses a separate Python venv for heavy document libraries.
188
+ Set `DOCS_VENV_PYTHON` to point to it:
189
+
190
+ ```bash
191
+ # Create a dedicated venv
192
+ python -m venv ~/.venvs/docs
193
+ ~/.venvs/docs/bin/pip install python-docx openpyxl python-pptx weasyprint odfpy Pillow lxml PyMuPDF firecrawl-anydoc liteparse
194
+ ```
195
+
196
+ Or install the libraries into your main Python environment:
197
+
198
+ ```bash
199
+ pip install tabella-docs[create]
200
+ ```
201
+
202
+ ### Read module
203
+
204
+ ```bash
205
+ ~/.venvs/docs/bin/pip install firecrawl-anydoc liteparse PyMuPDF
206
+ ```
207
+
208
+ Or:
209
+
210
+ ```bash
211
+ pip install tabella-docs[read]
212
+ ```
213
+
214
+ ### Convert module
215
+
216
+ ```bash
217
+ pip install tabella-docs[convert]
218
+ ```
219
+
220
+ Requires system-installed pandoc and a LaTeX engine:
221
+
222
+ ```bash
223
+ # Debian/Ubuntu
224
+ sudo apt install pandoc texlive-xetex
225
+
226
+ # macOS
227
+ brew install pandoc basictex
228
+
229
+ # Arch
230
+ sudo pacman -S pandoc texlive
231
+ ```
232
+
233
+ ### WeasyPrint system dependencies (PDF creation)
234
+
235
+ ```bash
236
+ # Debian/Ubuntu
237
+ sudo apt install libcairo2 libpango-1.0-0 libgdk-pixbuf-2.0-0
238
+
239
+ # macOS
240
+ brew install cairo pango gdk-pixbuf
241
+ ```
242
+
243
+ ## Installation from source
244
+
245
+ ```bash
246
+ git clone https://github.com/dyiapanis/tabella.git
247
+ cd tabella
248
+ pip install -e ".[all]"
249
+ ```
250
+
251
+ ## Configuration
252
+
253
+ | Variable | Purpose | Default |
254
+ |----------|---------|---------|
255
+ | `DOCS_VENV_PYTHON` | Python binary for document libraries | `PLUGIN_DATA/venv/bin/python` or `~/.venvs/docs/bin/python` |
256
+ | `DOCS_OUTPUT_DIR` | Default output directory for generated documents | `~/Documents` |
257
+ | `PLUGIN_DATA` | Client-managed data directory (set by v1 client) | — |
258
+ | `PLUGIN_ROOT` | Plugin root directory (set by v1 client) | — |
259
+
260
+ ### Automatic bootstrap
261
+
262
+ On first startup, the MCP server checks if the docs venv exists. If not,
263
+ it runs `scripts/setup.sh` to create a venv in `PLUGIN_DATA` (provided by
264
+ the v1 client) or `~/.venvs/docs` as fallback, and installs all dependencies:
265
+ python-docx, openpyxl, python-pptx, weasyprint, odfpy, PyMuPDF,
266
+ firecrawl-anydoc, liteparse.
267
+
268
+ This means the v1 package is fully self-contained — no manual pip install
269
+ required. The bootstrap runs once; subsequent startups use the existing venv.
270
+
271
+ ## Usage
272
+
273
+ ### Create a PDF from HTML/CSS
274
+
275
+ ```python
276
+ from agent_docs.create import create_document
277
+
278
+ result = create_document(
279
+ format="pdf",
280
+ content={"html": "<html><body><h1>Hello</h1></body></html>"},
281
+ output_path="/tmp/output.pdf",
282
+ options={"page_size": "A4"}
283
+ )
284
+ ```
285
+
286
+ ### Read a DOCX file
287
+
288
+ ```python
289
+ from agent_docs.read import read
290
+
291
+ text = read("/path/to/document.docx")
292
+ # Returns GitHub-Flavored Markdown
293
+ ```
294
+
295
+ ### Convert Markdown to PDF
296
+
297
+ ```python
298
+ from agent_docs.convert import doc_convert
299
+
300
+ result = doc_convert(
301
+ input_path="/path/to/input.md",
302
+ to_format="pdf",
303
+ output_path="/path/to/output.pdf"
304
+ )
305
+ ```
306
+
307
+ ### Via MCP
308
+
309
+ The bundled MCP server exposes five tools: `doc_read`, `doc_read_meta`,
310
+ `doc_convert`, `doc_create_document`, `doc_edit_pdf`. Any MCP-compatible
311
+ client can call these directly.
312
+
313
+ ## License
314
+
315
+ MIT
@@ -0,0 +1,10 @@
1
+ tabella/__init__.py,sha256=ZWesd1vh6548Mhzaph7RjCZLKpyvVHTcR5w3LblDcZo,1580
2
+ tabella/convert.py,sha256=84a9V2IBjpZYNtnnTXCfGw15-DpczicEoUetOzi0GCg,6529
3
+ tabella/create.py,sha256=uDIpK7slDakjJ3vXFbuOjfeZs14r5ja1JgnccEwPb2k,31105
4
+ tabella/read.py,sha256=kJBYnLywYb3lYyfDyKPU2f3KxJC2Z5ogFQYMUSmOS2c,9423
5
+ tabella/utils.py,sha256=m5hSj9Bep-GS1NOZ7NZk5MT3ZAElH6Q2uNIzMom3HR0,1216
6
+ tabella_docs-2.0.1.dist-info/licenses/LICENSE,sha256=YY_7vguV9tONkLu76Y4S4Rhxkny4g2O-yY5pMq5KLqQ,1066
7
+ tabella_docs-2.0.1.dist-info/METADATA,sha256=8XDi7ykrGyDoDMa0dZOK_F34owrKT1s26NkzQJ9kerg,10025
8
+ tabella_docs-2.0.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
+ tabella_docs-2.0.1.dist-info/top_level.txt,sha256=ALebCE3nPaYfRNya0n5oiH6Llu4PJiiVRxuFHk0c9yo,8
10
+ tabella_docs-2.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 D Yiapanis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ tabella