trimitdown 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sergey Dreizin
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,6 @@
1
+ # В sdist приложения setuptools по умолчанию кладёт всё дерево tests/, причём
2
+ # неполным: conftest.py и pdf_fixtures.py туда не попадают, поэтому запустить эти
3
+ # тесты у потребителя всё равно нельзя. Публикуемый архив -- не место для лишней
4
+ # поверхности, тем более в проекте, который однажды уже опубликовал в тестовых
5
+ # данных то, чего публиковать не следовало.
6
+ prune tests
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: trimitdown
3
+ Version: 0.1.0
4
+ Summary: Convert documents to LLM-ready markdown. Ruled-table-aware PDF extraction, offline, no cloud.
5
+ Author: Sergey Dreizin
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/serjdrej/trimitdown
8
+ Project-URL: Repository, https://github.com/serjdrej/trimitdown
9
+ Project-URL: Issues, https://github.com/serjdrej/trimitdown/issues
10
+ Keywords: markdown,pdf,llm,document-conversion,cli
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: trimitdown-pdf==0.1.0
26
+ Requires-Dist: markitdown[docx,outlook,pdf,pptx,xls,xlsx]
27
+ Requires-Dist: tiktoken
28
+ Requires-Dist: python-pptx
29
+ Requires-Dist: pdfminer.six
30
+ Provides-Extra: server
31
+ Requires-Dist: fastapi; extra == "server"
32
+ Requires-Dist: uvicorn; extra == "server"
33
+ Requires-Dist: python-multipart; extra == "server"
34
+ Dynamic: license-file
35
+
36
+ <div align="center">
37
+
38
+ <picture>
39
+ <source media="(prefers-color-scheme: dark)" srcset="brand/wordmark_dark.svg">
40
+ <img src="brand/wordmark_light.svg" alt="TrimItDown" width="360">
41
+ </picture>
42
+
43
+ **Any document → clean, LLM-ready Markdown. On your own server, on every device you own.**
44
+
45
+ *Read this in: English (this file) · [русский](README.ru.md)*
46
+
47
+ [![Latest release](https://img.shields.io/github/v/release/serjdrej/trimitdown)](https://github.com/serjdrej/trimitdown/releases/latest)
48
+ [![Downloads](https://img.shields.io/github/downloads/serjdrej/trimitdown/total)](https://github.com/serjdrej/trimitdown/releases)
49
+ [![macOS build](https://github.com/serjdrej/trimitdown/actions/workflows/build-macos.yml/badge.svg)](https://github.com/serjdrej/trimitdown/actions/workflows/build-macos.yml)
50
+ [![PyPI](https://img.shields.io/pypi/v/trimitdown-pdf?label=trimitdown-pdf&color=3775A9)](https://pypi.org/project/trimitdown-pdf/)
51
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
52
+ ![Platforms](https://img.shields.io/badge/platforms-iOS%20PWA%20·%20Windows%20·%20macOS%20·%20Docker-8A6FE8)
53
+
54
+ <picture>
55
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/result-dark.png">
56
+ <img src="docs/images/result-light.png" alt="TrimItDown converting a PDF: live Markdown preview with a real table and a token counter" width="850">
57
+ </picture>
58
+
59
+ </div>
60
+
61
+ TrimItDown turns PDF, Word (docx), PowerPoint (pptx), Excel (xlsx/xls) and Outlook (.msg)
62
+ files into clean, readable Markdown — ready to paste into Claude/ChatGPT, Obsidian, Notion,
63
+ or any markdown vault. It runs as an iPhone/iPad app (installed straight from Safari, no App
64
+ Store), a portable Windows program, a macOS app, and a self-hosted Docker server — with one
65
+ shared archive of conversions across all your devices.
66
+
67
+ ## The PDF engine
68
+
69
+ Most converters — including the stock [MarkItDown](https://github.com/microsoft/markitdown)
70
+ PDF path — stumble on real-world PDFs in three measurable ways: they **glue words together**,
71
+ **invent tables out of ordinary prose**, and **drop genuine ruled tables**. Measured over 891
72
+ real documents from two independent collections, the stock converter emitted 5,624 table rows
73
+ on documents containing no ruled grid at all — prose reshaped into tables — against 2 from the
74
+ engine below. On the 885 of those documents that are not broken at the source, it roughly
75
+ halves glued words (107 runs vs 53). Those corpora are third-party
76
+ copyrighted material and are not redistributable — the full method, the numbers, the cases
77
+ where this engine *loses*, and what a reader **can** reproduce are in
78
+ [docs/pdf-engine.md](docs/pdf-engine.md).
79
+
80
+ So TrimItDown replaced it with a custom extraction engine:
81
+
82
+ - **Words split at a measured word-gap threshold** — a fraction of the font size, not a fixed
83
+ point value, so it holds across small print and large headings alike.
84
+ - **A dedicated table-detection stage** validates every candidate grid by how its cells are
85
+ actually filled (a row-fill vote), instead of trusting every ruled rectangle. Diagrams and
86
+ decorative frames get rejected; their text flows back into prose instead of vanishing.
87
+ - **Genuine ruled tables render as honest Markdown tables**, cell for cell.
88
+
89
+ Here is one document converted both ways. The file is committed to this repo, so you can run
90
+ the comparison yourself:
91
+
92
+ ```bash
93
+ python scripts/compare_pdf_engines.py tests/data/sample-service-report.pdf
94
+ ```
95
+
96
+ **Stock converter** — a phantom empty column splits the data, shifting every value one column
97
+ right of its header:
98
+
99
+ ```markdown
100
+ | Parameter | Unit | Before service | | After service | Limit |
101
+ | ------------------------- | ---- | -------------- | --- | ------------- | ----- |
102
+ | Supply airflow | m³/h | | 352 | 398 | ≥ 380 |
103
+ | Extract airflow | m³/h | | 341 | 402 | ≥ 380 |
104
+ | Filter pressure drop | Pa | | 184 | 92 | ≤ 150 |
105
+ | Heat recovery efficiency | % | | 61 | 78 | ≥ 70 |
106
+ | Motor current, supply fan | A | | 1.9 | 1.6 | ≤ 2.2 |
107
+ ```
108
+
109
+ **TrimItDown** — the table as it appears on the page:
110
+
111
+ ```markdown
112
+ | Parameter | Unit | Before service | After service | Limit |
113
+ | --- | --- | --- | --- | --- |
114
+ | Supply airflow | m³/h | 352 | 398 | ≥ 380 |
115
+ | Extract airflow | m³/h | 341 | 402 | ≥ 380 |
116
+ | Filter pressure drop | Pa | 184 | 92 | ≤ 150 |
117
+ | Heat recovery efficiency | % | 61 | 78 | ≥ 70 |
118
+ | Motor current, supply fan | A | 1.9 | 1.6 | ≤ 2.2 |
119
+ ```
120
+
121
+ One sample proves nothing about a corpus, and ours is third-party material we cannot publish.
122
+ So the measurement ships instead of the documents — point it at your own PDFs and it runs both
123
+ converters over all of them and prints a comparable summary, counts only, no filenames:
124
+
125
+ ```bash
126
+ python scripts/measure_corpus.py /path/to/your/pdfs --limit 50
127
+ ```
128
+
129
+ **Please send that summary back whatever it says** — a win, a loss, or a tie
130
+ ([issue template](https://github.com/serjdrej/trimitdown/issues/new?template=measurement.yml)).
131
+ Results collected only when something breaks are a sample made of breakage, and nothing
132
+ honest can be concluded from it. Pooled across enough corpora, these numbers stop being one
133
+ person's collection. Details in
134
+ [docs/pdf-engine.md](docs/pdf-engine.md#re-measuring-on-your-own-pdfs).
135
+
136
+ In short: we add the table-*validation* stage that the classic tabula-java pipeline has and
137
+ Python extractors lack, expressed as a cell-fill vote on pdfplumber's ruled grids, with
138
+ per-grid fallback to prose — no ML models, no cloud, small enough to ship inside a portable
139
+ binary. Every non-PDF format still goes through MarkItDown.
140
+
141
+ ## Why TrimItDown
142
+
143
+ - **Made for the LLM workflow.** Clean Markdown out, a live preview, and a token counter that
144
+ shows what a document will cost before you paste it into a model's context.
145
+ - **Your files never leave your infrastructure.** Conversion happens on your own server (home
146
+ NAS, VPS) or fully offline on your computer. No third-party SaaS, no per-page fees.
147
+ - **One archive, every device.** Convert on your phone — the result is already on your
148
+ computer, and vice versa. Searchable, with batch conversion and ZIP export.
149
+ - **A real app experience everywhere.** iPhone PWA installed from Safari (no App Store), a
150
+ portable Windows exe, a macOS app. Russian and English UI, light/dark, two color themes.
151
+
152
+ ## Get it
153
+
154
+ | Platform | How |
155
+ |---|---|
156
+ | **Windows** | Download `TrimItDown-windows-x64.exe` from [Releases](https://github.com/serjdrej/trimitdown/releases/latest) — portable, no install |
157
+ | **macOS** (Apple Silicon / Intel) | Download the matching `.zip` from [Releases](https://github.com/serjdrej/trimitdown/releases/latest), unzip, right-click → Open |
158
+ | **iPhone / iPad** | Served by your own Docker server — open it in Safari → Share → *Add to Home Screen* |
159
+ | **Docker server** | See [Self-hosting](#self-hosting) below |
160
+
161
+ The desktop apps work fully offline out of the box. Point them at your server in **Settings**
162
+ to get the shared archive.
163
+
164
+ ## Command-line package
165
+
166
+ Run a conversion without installing anything permanently:
167
+
168
+ ```bash
169
+ uvx trimitdown convert report.pdf > report.md
170
+ ```
171
+
172
+ Or install it with `pip install trimitdown` and write directly to a file with
173
+ `trimitdown convert report.pdf -o report.md`. Without `-o`, Markdown goes to standard output.
174
+ The CLI works fully offline.
175
+
176
+ ## Self-hosting
177
+
178
+ The Docker server is the source of truth: it converts, stores the shared archive, and serves
179
+ the iPhone PWA.
180
+
181
+ ```bash
182
+ git clone https://github.com/serjdrej/trimitdown.git
183
+ cd trimitdown/docker-server
184
+ # one-time: generate a self-signed HTTPS certificate (copy-paste command in docker-server/README.md)
185
+ docker-compose up -d --build
186
+ ```
187
+
188
+ Then open `https://YOUR_SERVER:8002`. Full instructions — certificate generation, trusting it
189
+ on iOS/Windows/macOS, and the API — in [`docker-server/README.en.md`](docker-server/README.en.md).
190
+
191
+ ## Screenshots
192
+
193
+ | Archive, shared across devices | Settings |
194
+ |---|---|
195
+ | ![Archive with search](docs/images/archive-light.png) | ![Settings screen](docs/images/settings-dark.png) |
196
+
197
+ | Ocean theme, light | Ocean theme, dark |
198
+ |---|---|
199
+ | ![Ocean light theme](docs/images/home-ocean-light.png) | ![Ocean dark theme](docs/images/home-ocean-dark.png) |
200
+
201
+ | The iPhone PWA — home screen | Converting a PDF |
202
+ |---|---|
203
+ | <img src="docs/images/iphone-home-en.png" alt="TrimItDown PWA on iPhone, empty state" width="260"> | <img src="docs/images/iphone-result-en.png" alt="TrimItDown PWA on iPhone, conversion result" width="260"> |
204
+
205
+ ## How it works
206
+
207
+ - **The Docker server** is a FastAPI service with HTTPS; the archive lives on the server. The
208
+ same service serves the PWA for iPhone.
209
+ - **The Windows/macOS apps** check at startup whether your server is reachable: if yes, they
210
+ open straight on it (shared archive); if not, they spin up a bundled local server and work
211
+ fully offline. The shell is [pywebview](https://pywebview.flowrl.com/) (WebView2 / WKWebView),
212
+ packaged with PyInstaller.
213
+ - **Conversion:** PDFs go through TrimItDown's own engine ([`trimitdown-pdf`](packages/trimitdown-pdf),
214
+ built on pdfplumber); every other format goes through Microsoft's
215
+ [MarkItDown](https://github.com/microsoft/markitdown).
216
+
217
+ ## Repository layout
218
+
219
+ - [`src/trimitdown/`](src/trimitdown/) — published CLI package and conversion logic
220
+ - [`core/`](core/) — HTTP-layer and archive support for the desktop and server applications
221
+ - [`packages/trimitdown-pdf/`](packages/trimitdown-pdf/) — separately published PDF engine
222
+ - [`docker-server/`](docker-server/) — the self-hosted service + iPhone PWA
223
+ - [`static/`](static/), [`main.py`](main.py), [`server_app.py`](server_app.py) — desktop apps (UI, entry point, offline mode)
224
+ - [`tests/`](tests/) — unit tests + a labeled corpus harness for the table-detection stage
225
+ - [`docs/pdf-engine.md`](docs/pdf-engine.md) — PDF engine design, measurements, reproduction
226
+
227
+ ## Contributing
228
+
229
+ Bug reports and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
230
+ [DEVELOPMENT.md](DEVELOPMENT.md) documents the dev setup and the desktop build internals.
231
+
232
+ ## Limitations
233
+
234
+ - The server's self-signed HTTPS certificate needs a one-time manual trust on each device
235
+ (iOS: profile + full trust; Windows: import into `CurrentUser\Root`; macOS: Keychain).
236
+ - The binaries aren't code-signed (no Apple/Microsoft developer certificate) — first launch
237
+ needs a one-time override (macOS: right-click → Open; Windows: SmartScreen).
238
+
239
+ ## License and credits
240
+
241
+ Code is [MIT](LICENSE). Non-PDF conversion is powered by
242
+ [MarkItDown](https://github.com/microsoft/markitdown) (Microsoft, MIT); the bundled
243
+ third-party licenses are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,208 @@
1
+ <div align="center">
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="brand/wordmark_dark.svg">
5
+ <img src="brand/wordmark_light.svg" alt="TrimItDown" width="360">
6
+ </picture>
7
+
8
+ **Any document → clean, LLM-ready Markdown. On your own server, on every device you own.**
9
+
10
+ *Read this in: English (this file) · [русский](README.ru.md)*
11
+
12
+ [![Latest release](https://img.shields.io/github/v/release/serjdrej/trimitdown)](https://github.com/serjdrej/trimitdown/releases/latest)
13
+ [![Downloads](https://img.shields.io/github/downloads/serjdrej/trimitdown/total)](https://github.com/serjdrej/trimitdown/releases)
14
+ [![macOS build](https://github.com/serjdrej/trimitdown/actions/workflows/build-macos.yml/badge.svg)](https://github.com/serjdrej/trimitdown/actions/workflows/build-macos.yml)
15
+ [![PyPI](https://img.shields.io/pypi/v/trimitdown-pdf?label=trimitdown-pdf&color=3775A9)](https://pypi.org/project/trimitdown-pdf/)
16
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
17
+ ![Platforms](https://img.shields.io/badge/platforms-iOS%20PWA%20·%20Windows%20·%20macOS%20·%20Docker-8A6FE8)
18
+
19
+ <picture>
20
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/result-dark.png">
21
+ <img src="docs/images/result-light.png" alt="TrimItDown converting a PDF: live Markdown preview with a real table and a token counter" width="850">
22
+ </picture>
23
+
24
+ </div>
25
+
26
+ TrimItDown turns PDF, Word (docx), PowerPoint (pptx), Excel (xlsx/xls) and Outlook (.msg)
27
+ files into clean, readable Markdown — ready to paste into Claude/ChatGPT, Obsidian, Notion,
28
+ or any markdown vault. It runs as an iPhone/iPad app (installed straight from Safari, no App
29
+ Store), a portable Windows program, a macOS app, and a self-hosted Docker server — with one
30
+ shared archive of conversions across all your devices.
31
+
32
+ ## The PDF engine
33
+
34
+ Most converters — including the stock [MarkItDown](https://github.com/microsoft/markitdown)
35
+ PDF path — stumble on real-world PDFs in three measurable ways: they **glue words together**,
36
+ **invent tables out of ordinary prose**, and **drop genuine ruled tables**. Measured over 891
37
+ real documents from two independent collections, the stock converter emitted 5,624 table rows
38
+ on documents containing no ruled grid at all — prose reshaped into tables — against 2 from the
39
+ engine below. On the 885 of those documents that are not broken at the source, it roughly
40
+ halves glued words (107 runs vs 53). Those corpora are third-party
41
+ copyrighted material and are not redistributable — the full method, the numbers, the cases
42
+ where this engine *loses*, and what a reader **can** reproduce are in
43
+ [docs/pdf-engine.md](docs/pdf-engine.md).
44
+
45
+ So TrimItDown replaced it with a custom extraction engine:
46
+
47
+ - **Words split at a measured word-gap threshold** — a fraction of the font size, not a fixed
48
+ point value, so it holds across small print and large headings alike.
49
+ - **A dedicated table-detection stage** validates every candidate grid by how its cells are
50
+ actually filled (a row-fill vote), instead of trusting every ruled rectangle. Diagrams and
51
+ decorative frames get rejected; their text flows back into prose instead of vanishing.
52
+ - **Genuine ruled tables render as honest Markdown tables**, cell for cell.
53
+
54
+ Here is one document converted both ways. The file is committed to this repo, so you can run
55
+ the comparison yourself:
56
+
57
+ ```bash
58
+ python scripts/compare_pdf_engines.py tests/data/sample-service-report.pdf
59
+ ```
60
+
61
+ **Stock converter** — a phantom empty column splits the data, shifting every value one column
62
+ right of its header:
63
+
64
+ ```markdown
65
+ | Parameter | Unit | Before service | | After service | Limit |
66
+ | ------------------------- | ---- | -------------- | --- | ------------- | ----- |
67
+ | Supply airflow | m³/h | | 352 | 398 | ≥ 380 |
68
+ | Extract airflow | m³/h | | 341 | 402 | ≥ 380 |
69
+ | Filter pressure drop | Pa | | 184 | 92 | ≤ 150 |
70
+ | Heat recovery efficiency | % | | 61 | 78 | ≥ 70 |
71
+ | Motor current, supply fan | A | | 1.9 | 1.6 | ≤ 2.2 |
72
+ ```
73
+
74
+ **TrimItDown** — the table as it appears on the page:
75
+
76
+ ```markdown
77
+ | Parameter | Unit | Before service | After service | Limit |
78
+ | --- | --- | --- | --- | --- |
79
+ | Supply airflow | m³/h | 352 | 398 | ≥ 380 |
80
+ | Extract airflow | m³/h | 341 | 402 | ≥ 380 |
81
+ | Filter pressure drop | Pa | 184 | 92 | ≤ 150 |
82
+ | Heat recovery efficiency | % | 61 | 78 | ≥ 70 |
83
+ | Motor current, supply fan | A | 1.9 | 1.6 | ≤ 2.2 |
84
+ ```
85
+
86
+ One sample proves nothing about a corpus, and ours is third-party material we cannot publish.
87
+ So the measurement ships instead of the documents — point it at your own PDFs and it runs both
88
+ converters over all of them and prints a comparable summary, counts only, no filenames:
89
+
90
+ ```bash
91
+ python scripts/measure_corpus.py /path/to/your/pdfs --limit 50
92
+ ```
93
+
94
+ **Please send that summary back whatever it says** — a win, a loss, or a tie
95
+ ([issue template](https://github.com/serjdrej/trimitdown/issues/new?template=measurement.yml)).
96
+ Results collected only when something breaks are a sample made of breakage, and nothing
97
+ honest can be concluded from it. Pooled across enough corpora, these numbers stop being one
98
+ person's collection. Details in
99
+ [docs/pdf-engine.md](docs/pdf-engine.md#re-measuring-on-your-own-pdfs).
100
+
101
+ In short: we add the table-*validation* stage that the classic tabula-java pipeline has and
102
+ Python extractors lack, expressed as a cell-fill vote on pdfplumber's ruled grids, with
103
+ per-grid fallback to prose — no ML models, no cloud, small enough to ship inside a portable
104
+ binary. Every non-PDF format still goes through MarkItDown.
105
+
106
+ ## Why TrimItDown
107
+
108
+ - **Made for the LLM workflow.** Clean Markdown out, a live preview, and a token counter that
109
+ shows what a document will cost before you paste it into a model's context.
110
+ - **Your files never leave your infrastructure.** Conversion happens on your own server (home
111
+ NAS, VPS) or fully offline on your computer. No third-party SaaS, no per-page fees.
112
+ - **One archive, every device.** Convert on your phone — the result is already on your
113
+ computer, and vice versa. Searchable, with batch conversion and ZIP export.
114
+ - **A real app experience everywhere.** iPhone PWA installed from Safari (no App Store), a
115
+ portable Windows exe, a macOS app. Russian and English UI, light/dark, two color themes.
116
+
117
+ ## Get it
118
+
119
+ | Platform | How |
120
+ |---|---|
121
+ | **Windows** | Download `TrimItDown-windows-x64.exe` from [Releases](https://github.com/serjdrej/trimitdown/releases/latest) — portable, no install |
122
+ | **macOS** (Apple Silicon / Intel) | Download the matching `.zip` from [Releases](https://github.com/serjdrej/trimitdown/releases/latest), unzip, right-click → Open |
123
+ | **iPhone / iPad** | Served by your own Docker server — open it in Safari → Share → *Add to Home Screen* |
124
+ | **Docker server** | See [Self-hosting](#self-hosting) below |
125
+
126
+ The desktop apps work fully offline out of the box. Point them at your server in **Settings**
127
+ to get the shared archive.
128
+
129
+ ## Command-line package
130
+
131
+ Run a conversion without installing anything permanently:
132
+
133
+ ```bash
134
+ uvx trimitdown convert report.pdf > report.md
135
+ ```
136
+
137
+ Or install it with `pip install trimitdown` and write directly to a file with
138
+ `trimitdown convert report.pdf -o report.md`. Without `-o`, Markdown goes to standard output.
139
+ The CLI works fully offline.
140
+
141
+ ## Self-hosting
142
+
143
+ The Docker server is the source of truth: it converts, stores the shared archive, and serves
144
+ the iPhone PWA.
145
+
146
+ ```bash
147
+ git clone https://github.com/serjdrej/trimitdown.git
148
+ cd trimitdown/docker-server
149
+ # one-time: generate a self-signed HTTPS certificate (copy-paste command in docker-server/README.md)
150
+ docker-compose up -d --build
151
+ ```
152
+
153
+ Then open `https://YOUR_SERVER:8002`. Full instructions — certificate generation, trusting it
154
+ on iOS/Windows/macOS, and the API — in [`docker-server/README.en.md`](docker-server/README.en.md).
155
+
156
+ ## Screenshots
157
+
158
+ | Archive, shared across devices | Settings |
159
+ |---|---|
160
+ | ![Archive with search](docs/images/archive-light.png) | ![Settings screen](docs/images/settings-dark.png) |
161
+
162
+ | Ocean theme, light | Ocean theme, dark |
163
+ |---|---|
164
+ | ![Ocean light theme](docs/images/home-ocean-light.png) | ![Ocean dark theme](docs/images/home-ocean-dark.png) |
165
+
166
+ | The iPhone PWA — home screen | Converting a PDF |
167
+ |---|---|
168
+ | <img src="docs/images/iphone-home-en.png" alt="TrimItDown PWA on iPhone, empty state" width="260"> | <img src="docs/images/iphone-result-en.png" alt="TrimItDown PWA on iPhone, conversion result" width="260"> |
169
+
170
+ ## How it works
171
+
172
+ - **The Docker server** is a FastAPI service with HTTPS; the archive lives on the server. The
173
+ same service serves the PWA for iPhone.
174
+ - **The Windows/macOS apps** check at startup whether your server is reachable: if yes, they
175
+ open straight on it (shared archive); if not, they spin up a bundled local server and work
176
+ fully offline. The shell is [pywebview](https://pywebview.flowrl.com/) (WebView2 / WKWebView),
177
+ packaged with PyInstaller.
178
+ - **Conversion:** PDFs go through TrimItDown's own engine ([`trimitdown-pdf`](packages/trimitdown-pdf),
179
+ built on pdfplumber); every other format goes through Microsoft's
180
+ [MarkItDown](https://github.com/microsoft/markitdown).
181
+
182
+ ## Repository layout
183
+
184
+ - [`src/trimitdown/`](src/trimitdown/) — published CLI package and conversion logic
185
+ - [`core/`](core/) — HTTP-layer and archive support for the desktop and server applications
186
+ - [`packages/trimitdown-pdf/`](packages/trimitdown-pdf/) — separately published PDF engine
187
+ - [`docker-server/`](docker-server/) — the self-hosted service + iPhone PWA
188
+ - [`static/`](static/), [`main.py`](main.py), [`server_app.py`](server_app.py) — desktop apps (UI, entry point, offline mode)
189
+ - [`tests/`](tests/) — unit tests + a labeled corpus harness for the table-detection stage
190
+ - [`docs/pdf-engine.md`](docs/pdf-engine.md) — PDF engine design, measurements, reproduction
191
+
192
+ ## Contributing
193
+
194
+ Bug reports and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
195
+ [DEVELOPMENT.md](DEVELOPMENT.md) documents the dev setup and the desktop build internals.
196
+
197
+ ## Limitations
198
+
199
+ - The server's self-signed HTTPS certificate needs a one-time manual trust on each device
200
+ (iOS: profile + full trust; Windows: import into `CurrentUser\Root`; macOS: Keychain).
201
+ - The binaries aren't code-signed (no Apple/Microsoft developer certificate) — first launch
202
+ needs a one-time override (macOS: right-click → Open; Windows: SmartScreen).
203
+
204
+ ## License and credits
205
+
206
+ Code is [MIT](LICENSE). Non-PDF conversion is powered by
207
+ [MarkItDown](https://github.com/microsoft/markitdown) (Microsoft, MIT); the bundled
208
+ third-party licenses are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,73 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "trimitdown"
7
+ # Единственный источник -- __version__ в модуле, читается статически через AST,
8
+ # без импорта: иначе в build-окружение потянулся бы markitdown.
9
+ dynamic = ["version"]
10
+ description = "Convert documents to LLM-ready markdown. Ruled-table-aware PDF extraction, offline, no cloud."
11
+ readme = "README.md"
12
+ requires-python = ">=3.10"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ authors = [{ name = "Sergey Dreizin" }]
16
+ keywords = ["markdown", "pdf", "llm", "document-conversion", "cli"]
17
+ # Классификатор лицензии не добавлять: PEP 639 запрещает его вместе с
18
+ # выражением license = "MIT", setuptools>=77 падает.
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Environment :: Console",
22
+ # Developers rather than desktop end users: what ships to PyPI is the CLI,
23
+ # and the desktop app never arrives through pip at all -- it comes via
24
+ # brew/scoop. The audience classifier naming desktop end users also happens
25
+ # to contain a substring the PII guard treats as a user path; see .githooks.
26
+ "Intended Audience :: Developers",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Text Processing :: Markup :: Markdown",
34
+ "Topic :: Utilities",
35
+ ]
36
+
37
+ # Точный пин, не диапазон: диапазон дал бы молчаливый подъём движка при
38
+ # пересборке, а движок определяет качество вывода. Подъём -- отдельный коммит.
39
+ # FastAPI/uvicorn здесь НЕТ: это CLI-пакет, веб-стек живёт в extra "server".
40
+ dependencies = [
41
+ "trimitdown-pdf==0.1.0",
42
+ # Same narrow extras as requirements.txt, and for the same reason recorded
43
+ # there: the audio-transcription and Azure extras are heavy and useless
44
+ # offline, which is what this package claims to be. "all" would pull an
45
+ # Azure client into every install.
46
+ "markitdown[docx,pdf,pptx,xlsx,xls,outlook]",
47
+ "tiktoken",
48
+ "python-pptx",
49
+ "pdfminer.six",
50
+ ]
51
+
52
+ [project.optional-dependencies]
53
+ # Нужен только тому, кто поднимает HTTP-слой (Docker-образ, десктопная сборка).
54
+ server = ["fastapi", "uvicorn", "python-multipart"]
55
+
56
+ [project.scripts]
57
+ trimitdown = "trimitdown.cli:main"
58
+
59
+ [project.urls]
60
+ Homepage = "https://github.com/serjdrej/trimitdown"
61
+ Repository = "https://github.com/serjdrej/trimitdown"
62
+ Issues = "https://github.com/serjdrej/trimitdown/issues"
63
+
64
+ [tool.setuptools.dynamic]
65
+ version = { attr = "trimitdown.__version__" }
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+
70
+ [tool.setuptools.package-data]
71
+ # Офлайн-кэш tiktoken. Без этой строки он не попадёт в wheel, и счёт токенов у
72
+ # установленного пакета молча уйдёт в сеть.
73
+ trimitdown = ["tiktoken_cache/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ """TrimItDown: конвертация документов в markdown, пригодный для вставки в LLM.
2
+
3
+ ЕДИНСТВЕННЫЙ источник версии для всего проекта: пакета на PyPI, десктопного
4
+ приложения, ответа /api/mode и того, что видно в UI. Раньше версия приложения
5
+ жила отдельно в core/version.py -- два числа неизбежно разъезжаются, и разъезд
6
+ замечает пользователь, а не мы.
7
+
8
+ Обязан остаться строковым литералом: setuptools читает его статически через AST,
9
+ не импортируя модуль, -- иначе в build-окружение потянется markitdown.
10
+ """
11
+
12
+ __version__ = "0.1.0"
@@ -0,0 +1,84 @@
1
+ """Командная строка: единственный интерфейс pip/uvx-пакета.
2
+
3
+ Только локальная конверсия. Работа с внешним сервером -- это канал Docker
4
+ (docker exec внутрь контейнера) и настройка в десктопном приложении; сетевого
5
+ режима здесь нет сознательно, чтобы у инструмента, который ценен локальностью,
6
+ не появилось сетевых отказов.
7
+ """
8
+ import argparse
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from trimitdown import __version__
13
+ from trimitdown.convert import ConversionError, convert_bytes, convert_path
14
+
15
+
16
+ def _build_parser() -> argparse.ArgumentParser:
17
+ parser = argparse.ArgumentParser(
18
+ prog="trimitdown",
19
+ description="Convert documents to LLM-ready markdown.",
20
+ )
21
+ parser.add_argument("--version", action="version", version=f"trimitdown {__version__}")
22
+
23
+ sub = parser.add_subparsers(dest="command", required=True)
24
+ convert = sub.add_parser("convert", help="convert one document to markdown")
25
+ convert.add_argument("path", help='file to convert, or "-" to read stdin')
26
+ convert.add_argument("-o", "--output", help="write markdown here instead of stdout")
27
+ convert.add_argument(
28
+ "--type",
29
+ dest="suffix",
30
+ help='file extension for stdin input, e.g. ".pdf" -- required with "-"',
31
+ )
32
+ return parser
33
+
34
+
35
+ def _run_convert(args) -> int:
36
+ if args.path == "-":
37
+ if not args.suffix:
38
+ print(
39
+ 'reading from stdin needs --type, e.g. --type .pdf: the extension '
40
+ "cannot be recovered from a stream",
41
+ file=sys.stderr,
42
+ )
43
+ return 2
44
+ try:
45
+ result = convert_bytes(sys.stdin.buffer.read(), args.suffix)
46
+ except ConversionError as e:
47
+ print(f"could not convert stdin: {e}", file=sys.stderr)
48
+ return 1
49
+ else:
50
+ path = Path(args.path)
51
+ if not path.is_file():
52
+ print(f"no such file: {path}", file=sys.stderr)
53
+ return 1
54
+ try:
55
+ result = convert_path(path)
56
+ except ConversionError as e:
57
+ print(f"could not convert {path}: {e}", file=sys.stderr)
58
+ return 1
59
+
60
+ if args.output:
61
+ Path(args.output).write_text(result.text, encoding="utf-8")
62
+ else:
63
+ # Явная запись в буфер: на Windows консольная кодировка по умолчанию не
64
+ # переваривает вывод конвертации (m³/h, ≥, кириллица) и печать падает.
65
+ sys.stdout.buffer.write(result.text.encode("utf-8"))
66
+ return 0
67
+
68
+
69
+ def main(argv: list[str] | None = None) -> int:
70
+ # Converted documents and the paths they came from carry characters the console
71
+ # codepage cannot encode (m³/h, ≥, Cyrillic); on a Russian Windows the first one
72
+ # would abort the process. stdout is written as bytes below for the document
73
+ # body, but error messages go through the text layer and need this.
74
+ for stream in (sys.stdout, sys.stderr):
75
+ stream.reconfigure(encoding="utf-8", errors="replace")
76
+
77
+ args = _build_parser().parse_args(argv)
78
+ if args.command == "convert":
79
+ return _run_convert(args)
80
+ return 2
81
+
82
+
83
+ if __name__ == "__main__":
84
+ raise SystemExit(main())