zim-pro 0.2.1__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.
Files changed (44) hide show
  1. zim_pro-0.2.1/PKG-INFO +304 -0
  2. zim_pro-0.2.1/README.md +282 -0
  3. zim_pro-0.2.1/pyproject.toml +68 -0
  4. zim_pro-0.2.1/pyproject.toml.orig +53 -0
  5. zim_pro-0.2.1/src/zim_pro/__init__.py +36 -0
  6. zim_pro-0.2.1/src/zim_pro/ai.py +183 -0
  7. zim_pro-0.2.1/src/zim_pro/api/__init__.py +0 -0
  8. zim_pro-0.2.1/src/zim_pro/api/ai.py +620 -0
  9. zim_pro-0.2.1/src/zim_pro/api/catalog.py +146 -0
  10. zim_pro-0.2.1/src/zim_pro/api/content.py +119 -0
  11. zim_pro-0.2.1/src/zim_pro/api/downloads.py +86 -0
  12. zim_pro-0.2.1/src/zim_pro/api/library.py +255 -0
  13. zim_pro-0.2.1/src/zim_pro/catalog/__init__.py +0 -0
  14. zim_pro-0.2.1/src/zim_pro/catalog/opds.py +103 -0
  15. zim_pro-0.2.1/src/zim_pro/catalog/updates.py +52 -0
  16. zim_pro-0.2.1/src/zim_pro/config.py +56 -0
  17. zim_pro-0.2.1/src/zim_pro/context.py +47 -0
  18. zim_pro-0.2.1/src/zim_pro/db.py +269 -0
  19. zim_pro-0.2.1/src/zim_pro/downloads/__init__.py +0 -0
  20. zim_pro-0.2.1/src/zim_pro/downloads/engine.py +314 -0
  21. zim_pro-0.2.1/src/zim_pro/downloads/manager.py +373 -0
  22. zim_pro-0.2.1/src/zim_pro/downloads/metalink.py +69 -0
  23. zim_pro-0.2.1/src/zim_pro/events.py +42 -0
  24. zim_pro-0.2.1/src/zim_pro/library/__init__.py +0 -0
  25. zim_pro-0.2.1/src/zim_pro/library/archive_pool.py +35 -0
  26. zim_pro-0.2.1/src/zim_pro/library/article.py +264 -0
  27. zim_pro-0.2.1/src/zim_pro/library/naming.py +44 -0
  28. zim_pro-0.2.1/src/zim_pro/library/reader.py +115 -0
  29. zim_pro-0.2.1/src/zim_pro/library/scanner.py +180 -0
  30. zim_pro-0.2.1/src/zim_pro/library/search.py +71 -0
  31. zim_pro-0.2.1/src/zim_pro/main.py +88 -0
  32. zim_pro-0.2.1/src/zim_pro/mcp_server.py +186 -0
  33. zim_pro-0.2.1/src/zim_pro/providers.py +573 -0
  34. zim_pro-0.2.1/src/zim_pro/retrieval.py +534 -0
  35. zim_pro-0.2.1/src/zim_pro/scheduler.py +62 -0
  36. zim_pro-0.2.1/src/zim_pro/web/apple-touch-icon.png +0 -0
  37. zim_pro-0.2.1/src/zim_pro/web/assets/index-C7MC6g9M.css +2 -0
  38. zim_pro-0.2.1/src/zim_pro/web/assets/index-CdDxwSvD.js +52 -0
  39. zim_pro-0.2.1/src/zim_pro/web/icon-192.png +0 -0
  40. zim_pro-0.2.1/src/zim_pro/web/icon-maskable.png +0 -0
  41. zim_pro-0.2.1/src/zim_pro/web/icon.png +0 -0
  42. zim_pro-0.2.1/src/zim_pro/web/index.html +35 -0
  43. zim_pro-0.2.1/src/zim_pro/web/manifest.webmanifest +14 -0
  44. zim_pro-0.2.1/src/zim_pro/websearch.py +85 -0
zim_pro-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.3
2
+ Name: zim-pro
3
+ Version: 0.2.1
4
+ Summary: Offline AI knowledge base: read, search and ask your ZIM library (Wikipedia and the Kiwix catalog) with a local or hosted model, and serve it to LLM apps over MCP.
5
+ Keywords: zim,kiwix,offline,wikipedia,mcp,rag,knowledge-base
6
+ Author: Kuldip Satpute
7
+ Author-email: Kuldip Satpute <satputekuldip@gmail.com>
8
+ Requires-Dist: fastapi>=0.143.0
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: libzim>=3.13.1
11
+ Requires-Dist: markdownify>=1.2.3
12
+ Requires-Dist: mcp>=2.3.0
13
+ Requires-Dist: pydantic-settings>=2.15.0
14
+ Requires-Dist: selectolax>=1.0.0
15
+ Requires-Dist: uvicorn[standard]>=0.54.0
16
+ Requires-Python: >=3.13
17
+ Project-URL: Homepage, https://github.com/magicstack-llp/zim-pro
18
+ Project-URL: Repository, https://github.com/magicstack-llp/zim-pro
19
+ Project-URL: Changelog, https://github.com/magicstack-llp/zim-pro/blob/master/CHANGELOG.md
20
+ Project-URL: Issues, https://github.com/magicstack-llp/zim-pro/issues
21
+ Description-Content-Type: text/markdown
22
+
23
+ <p align="center">
24
+ <img src="https://raw.githubusercontent.com/magicstack-llp/zim-pro/master/assets/logo.png" alt="Zim Pro logo: an elephant with a ball" width="180">
25
+ </p>
26
+
27
+ <h1 align="center">Zim Pro</h1>
28
+
29
+ **An offline AI knowledge base.** Zim Pro turns a folder of [ZIM](https://openzim.org) files
30
+ (offline Wikipedia, Stack Exchange, DevDocs, iFixit and the rest of the
31
+ [Kiwix catalog](https://library.kiwix.org)) into a library you can read, search and question,
32
+ on your own machine.
33
+
34
+ - **Knowledge that stays with you.** The books live on your disk and are read, searched and
35
+ kept up to date by your own server.
36
+ - **AI that answers from them.** Ask a question and the answer is written from your books,
37
+ with citations you can open and the pictures and videos from the pages it used.
38
+ - **Fully offline when you want it.** Point it at a local model (Ollama, LM Studio,
39
+ llama.cpp) and nothing leaves your network: no internet is needed to read, search or ask.
40
+ Hosted models and web search are there if you choose them, and the internet is otherwise
41
+ only used to download and update books.
42
+ - **A knowledge source for your other AI tools.** The same library is served over MCP, so
43
+ Claude and other LLM apps can search and read it.
44
+
45
+ It replaces `kiwix-serve`.
46
+
47
+ ## What it does
48
+
49
+ - **Read** – a reader for every book, with in-book search and title suggestions. Entries are
50
+ served at `/content/<book>/<path>` as `kiwix-serve` does, including `Range` for video.
51
+ - **Search** – full text within a book, or across the whole library with results grouped by
52
+ book, with a history of past searches.
53
+ - **Ask AI** – ask a question and get an answer written from your books, with numbered
54
+ citations, the source passages beside it, and the pictures and videos from those pages.
55
+ Works with any OpenAI-compatible chat API, hosted or local, and with two free providers
56
+ out of the box.
57
+ - **Markdown** – `/markdown/<book>/<path>` gives any article as Markdown: its text and
58
+ pictures, with links leading to the Markdown of other articles. The reader's **Markdown**
59
+ button shows it in place, with a copy button; videos and recordings play there too.
60
+ - **Updates** – compares each book with the Kiwix catalog and lists newer releases, with a
61
+ policy per book: automatic, ask first, or pinned.
62
+ - **Downloads** – multi-connection across the Kiwix mirrors, SHA-1 checked per 4 MiB piece
63
+ and SHA-256 checked at the end. Resumes after a pause, a restart or a crash.
64
+ - **Catalog** – browse the whole Kiwix library and add books.
65
+ - **MCP** – the library as tools for Claude and other LLM apps: streamable HTTP at `/mcp`,
66
+ or stdio with `zim-pro mcp`.
67
+ - **Phone-friendly** – installable as a web app, with a layout made for small screens.
68
+
69
+ ## Run with Docker
70
+
71
+ The image is published at `ghcr.io/magicstack-llp/zim-pro` for amd64 and arm64. It needs two
72
+ folders: your ZIM files, mounted at `/data`, and a folder for its database, mounted at
73
+ `/state`. The container runs as the user you give it, who must be able to write to both
74
+ (to `/data` because that is where downloads and updates go).
75
+
76
+ ### docker run
77
+
78
+ ```sh
79
+ mkdir -p zims state # create them yourself, or Docker creates them as root
80
+ docker run -d --name zim-pro \
81
+ --restart unless-stopped \
82
+ --user "$(id -u):$(id -g)" \
83
+ -p 8083:8080 \
84
+ -v "$PWD/zims:/data" \
85
+ -v "$PWD/state:/state" \
86
+ --add-host host.docker.internal:host-gateway \
87
+ ghcr.io/magicstack-llp/zim-pro:latest
88
+ ```
89
+
90
+ Then open http://localhost:8083. `--add-host` is only needed for Ask AI to reach a model
91
+ server on the same machine (Ollama, LM Studio, llama.cpp).
92
+
93
+ ### docker compose
94
+
95
+ ```yaml
96
+ services:
97
+ zim-pro:
98
+ image: ghcr.io/magicstack-llp/zim-pro:latest
99
+ container_name: zim-pro
100
+ restart: unless-stopped
101
+ user: "1000:1000" # your own uid:gid, from `id -u` and `id -g`
102
+ ports:
103
+ - "8083:8080"
104
+ extra_hosts:
105
+ - "host.docker.internal:host-gateway"
106
+ volumes:
107
+ - ./zims:/data # your folder of .zim files
108
+ - ./state:/state
109
+ # environment:
110
+ # KIWIX_ADMIN_TOKEN: change-me # required for changes and /mcp when set
111
+ ```
112
+
113
+ ```sh
114
+ mkdir -p zims state
115
+ docker compose up -d
116
+ ```
117
+
118
+ ### Everyday commands
119
+
120
+ | To | docker compose | docker run |
121
+ |---|---|---|
122
+ | See the logs | `docker compose logs -f` | `docker logs -f zim-pro` |
123
+ | Stop | `docker compose stop` | `docker stop zim-pro` |
124
+ | Start again | `docker compose start` | `docker start zim-pro` |
125
+ | Update to the newest image | `docker compose pull && docker compose up -d` | `docker pull ghcr.io/magicstack-llp/zim-pro:latest`, then `docker rm -f zim-pro` and the `docker run` above |
126
+ | Remove the container | `docker compose down` | `docker rm -f zim-pro` |
127
+
128
+ Removing the container loses nothing: the books are in `zims` and the settings, chats and
129
+ download progress are in `state`. A download in progress when the container stops carries on
130
+ from where it was when it starts again.
131
+
132
+ To stay on one version, use a version tag in place of `latest`, such as `:0.2.1` or `:0.2`.
133
+
134
+ ### Build the image yourself
135
+
136
+ The `compose.yml` in this repository builds from source:
137
+
138
+ ```sh
139
+ mkdir -p state
140
+ docker compose up -d --build # http://localhost:8083
141
+ ```
142
+
143
+ It mounts `/mnt/storage/Media/Kiwix/data` as the library; change that path to your own
144
+ folder of ZIM files.
145
+
146
+ ## Run without Docker
147
+
148
+ Each [release](https://github.com/magicstack-llp/zim-pro/releases) has two other ways to run
149
+ it, with the web app included in both.
150
+
151
+ **Standalone executable** for Linux (x86-64 and arm64). One file, no Python needed:
152
+
153
+ ```sh
154
+ tar -xzf zim-pro-0.2.1-linux-x86_64.tar.gz
155
+ cd zim-pro-0.2.1-linux-x86_64
156
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state ./zim-pro # http://localhost:8080
157
+ ```
158
+
159
+ **Python package**, for Python 3.13 on Linux or macOS:
160
+
161
+ ```sh
162
+ pip install zim-pro # or: uvx zim-pro
163
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state zim-pro # http://localhost:8080
164
+ ```
165
+
166
+ `SHA256SUMS` in the release lists the checksum of every file.
167
+
168
+ ## Run from source
169
+
170
+ ```sh
171
+ (cd web && npm ci && npm run build)
172
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state uv run zim-pro
173
+ ```
174
+
175
+ For UI work, run `npm run dev` in `web/` (set `KIWIX_BACKEND` if the server is not on
176
+ `127.0.0.1:8080`).
177
+
178
+ ## Configuration
179
+
180
+ | Variable | Default | Meaning |
181
+ |---|---|---|
182
+ | `KIWIX_DATA_DIR` | `/data` | Folder of `.zim` files (scanned recursively) |
183
+ | `KIWIX_STATE_DIR` | `/state` | Where the SQLite database lives |
184
+ | `KIWIX_HOST` / `KIWIX_PORT` | `0.0.0.0` / `8080` | Listen address |
185
+ | `KIWIX_ADMIN_TOKEN` | unset | If set, changes and `/mcp` need `Authorization: Bearer <token>` |
186
+ | `KIWIX_CATALOG_URL` | Kiwix OPDS feed | Catalog to check for updates |
187
+
188
+ Download limits, the update schedule and policies are set in the web UI under Settings.
189
+
190
+ The project was first called Kiwix Updater. The `KIWIX_*` variables and the database file
191
+ (`kiwix-updater.sqlite3`) keep that name so existing installs carry on unchanged, and the
192
+ `kiwix-updater` command still works alongside `zim-pro`.
193
+
194
+ ## Ask AI
195
+
196
+ Add one or more providers under Settings → AI providers, then pick a default model. Any
197
+ server with the OpenAI `/chat/completions` API works: OpenAI, Google Gemini, Perplexity,
198
+ OpenRouter, Groq, Ollama, LM Studio, llama.cpp, vLLM. The model can be switched per message
199
+ in the chat box.
200
+
201
+ Each provider has a switch: turned off, it is kept with its key but not used or offered.
202
+
203
+ Two providers that need no account or key are added on first start, so Ask AI works straight
204
+ after installing: **OVHcloud AI Endpoints** (Llama 3.3 70B, Qwen, Mistral, gpt-oss; about two
205
+ requests a minute per model) and **Kilo Gateway**'s free models (about 200 requests an hour;
206
+ Kilo may log free-tier prompts for training). Both are rate-limited per IP address and can be
207
+ removed under Settings; a removed one is not added back.
208
+
209
+ For a model server on the Docker host, use `http://host.docker.internal:<port>/v1` (Ollama:
210
+ `11434`, LM Studio: `1234`), not `localhost`, and make the server listen on all interfaces
211
+ (for Ollama, `OLLAMA_HOST=0.0.0.0`).
212
+
213
+ How a question is answered (see `src/zim_pro/retrieval.py`):
214
+
215
+ 1. **Plan.** The model is shown the list of books and returns several short queries from
216
+ different angles, extra keywords a good article would contain, and the books most likely
217
+ to help. If it cannot be asked in time, the question's own words are used.
218
+ 2. **Search the relevant books in depth** with every query and keyword. These are the model's
219
+ picks, books named after the subject, and always the main encyclopedia.
220
+ 3. **Search every other book lightly** with the main queries.
221
+ 4. **Rank.** Articles found by several searches rise (reciprocal rank fusion); the best 24 are
222
+ read and scored on how much of the subject one passage covers, and only passages that fit
223
+ are sent to the model with the question.
224
+
225
+ Articles reach the model as Markdown (`library/article.py`), not HTML: navigation, pictures
226
+ and their captions, citation markers, reference lists, "See also" and "External links"
227
+ sections and licence footers are removed and links are flattened to their text, so the tokens
228
+ go on prose. Each passage is sent under the heading of the section it came from.
229
+
230
+ The pictures, videos and recordings on each source's page are listed with the source and
231
+ shown as thumbnails under it; they are not sent to the model.
232
+
233
+ Chats are stored in the database.
234
+
235
+ Every prompt carries formatting rules, and answers are passed through a repair step before
236
+ they are shown (unclosed code blocks, leaked reasoning, malformed lists, tables and citations),
237
+ since small models often get Markdown wrong. Your own standing instructions can be added
238
+ under Settings → Ask AI.
239
+
240
+ **Web search** (off by default) is used when the library has no good match, or for every
241
+ question while the Internet button in the chat box is on. Either:
242
+
243
+ - a *search service* (Tavily, Brave, Serper for Google results, or your own SearXNG), whose
244
+ results are added to the sources for your chat model, or
245
+ - a *searching model* that looks things up itself: Perplexity Sonar, OpenRouter's web add-on,
246
+ an OpenAI search model, or Gemini with Google Search.
247
+
248
+ **Read aloud** adds a listen button to answers, using the browser's built-in voice or a
249
+ provider's speech model (OpenAI, Gemini, Groq, or a local `/audio/speech` server).
250
+
251
+ ## MCP
252
+
253
+ ```sh
254
+ claude mcp add --transport http zim-pro http://localhost:8083/mcp
255
+ ```
256
+
257
+ Tools: `list_books`, `search`, `suggest`, `get_article`, `get_article_outline`,
258
+ `list_updates`, and `start_update` (off until enabled in Settings).
259
+
260
+ For a stdio client, run `zim-pro mcp` with `KIWIX_DATA_DIR` and `KIWIX_STATE_DIR` set.
261
+
262
+ ## Notes
263
+
264
+ - Articles are served from the same origin as the admin UI, as with `kiwix-serve`. Scripts
265
+ inside a ZIM could therefore call the API; set `KIWIX_ADMIN_TOKEN` if you load ZIMs you do
266
+ not trust or expose the port beyond your LAN.
267
+ - libzim holds Python's GIL, so a slow full-text search briefly delays other requests.
268
+ - libzim does not expose relevance scores, so cross-book search groups results by book
269
+ instead of ranking them together.
270
+
271
+ ## Tests
272
+
273
+ ```sh
274
+ uv run pytest
275
+ (cd web && npm test)
276
+ ```
277
+
278
+ ## Built with
279
+
280
+ Python 3.13, FastAPI and [python-libzim](https://github.com/openzim/python-libzim) on the
281
+ server, SQLite for state, and React, Vite and Tailwind for the web app. Zim Pro is an
282
+ independent project and is not affiliated with Kiwix.
283
+
284
+ ## Releasing
285
+
286
+ Pushing a tag of the form `v1.2.3` runs `.github/workflows/release.yml`:
287
+
288
+ 1. Lint and tests. Nothing is published if they fail.
289
+ 2. In parallel: the Docker image for amd64 and for arm64 (each on its own runner, then joined
290
+ and published to GHCR as `1.2.3`, `1.2` and `latest`), the Python wheel and source
291
+ archive (then uploaded to PyPI), and the standalone executables.
292
+ 3. Last, and only if all of those succeeded, the GitHub release, with the executables, the
293
+ wheel, the source archive and their checksums attached.
294
+
295
+ The package version comes from the tag, and the release notes from that version's section of
296
+ `CHANGELOG.md`, so move the entries under "Unreleased" to a new version heading before
297
+ tagging.
298
+
299
+ ```sh
300
+ git tag v0.2.1 && git push origin v0.2.1
301
+ ```
302
+
303
+ Every push to `master` and every pull request runs `.github/workflows/ci.yml`: Python lint
304
+ (`ruff check`), formatting check and tests, and the web app's type check, tests and build.
@@ -0,0 +1,282 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/magicstack-llp/zim-pro/master/assets/logo.png" alt="Zim Pro logo: an elephant with a ball" width="180">
3
+ </p>
4
+
5
+ <h1 align="center">Zim Pro</h1>
6
+
7
+ **An offline AI knowledge base.** Zim Pro turns a folder of [ZIM](https://openzim.org) files
8
+ (offline Wikipedia, Stack Exchange, DevDocs, iFixit and the rest of the
9
+ [Kiwix catalog](https://library.kiwix.org)) into a library you can read, search and question,
10
+ on your own machine.
11
+
12
+ - **Knowledge that stays with you.** The books live on your disk and are read, searched and
13
+ kept up to date by your own server.
14
+ - **AI that answers from them.** Ask a question and the answer is written from your books,
15
+ with citations you can open and the pictures and videos from the pages it used.
16
+ - **Fully offline when you want it.** Point it at a local model (Ollama, LM Studio,
17
+ llama.cpp) and nothing leaves your network: no internet is needed to read, search or ask.
18
+ Hosted models and web search are there if you choose them, and the internet is otherwise
19
+ only used to download and update books.
20
+ - **A knowledge source for your other AI tools.** The same library is served over MCP, so
21
+ Claude and other LLM apps can search and read it.
22
+
23
+ It replaces `kiwix-serve`.
24
+
25
+ ## What it does
26
+
27
+ - **Read** – a reader for every book, with in-book search and title suggestions. Entries are
28
+ served at `/content/<book>/<path>` as `kiwix-serve` does, including `Range` for video.
29
+ - **Search** – full text within a book, or across the whole library with results grouped by
30
+ book, with a history of past searches.
31
+ - **Ask AI** – ask a question and get an answer written from your books, with numbered
32
+ citations, the source passages beside it, and the pictures and videos from those pages.
33
+ Works with any OpenAI-compatible chat API, hosted or local, and with two free providers
34
+ out of the box.
35
+ - **Markdown** – `/markdown/<book>/<path>` gives any article as Markdown: its text and
36
+ pictures, with links leading to the Markdown of other articles. The reader's **Markdown**
37
+ button shows it in place, with a copy button; videos and recordings play there too.
38
+ - **Updates** – compares each book with the Kiwix catalog and lists newer releases, with a
39
+ policy per book: automatic, ask first, or pinned.
40
+ - **Downloads** – multi-connection across the Kiwix mirrors, SHA-1 checked per 4 MiB piece
41
+ and SHA-256 checked at the end. Resumes after a pause, a restart or a crash.
42
+ - **Catalog** – browse the whole Kiwix library and add books.
43
+ - **MCP** – the library as tools for Claude and other LLM apps: streamable HTTP at `/mcp`,
44
+ or stdio with `zim-pro mcp`.
45
+ - **Phone-friendly** – installable as a web app, with a layout made for small screens.
46
+
47
+ ## Run with Docker
48
+
49
+ The image is published at `ghcr.io/magicstack-llp/zim-pro` for amd64 and arm64. It needs two
50
+ folders: your ZIM files, mounted at `/data`, and a folder for its database, mounted at
51
+ `/state`. The container runs as the user you give it, who must be able to write to both
52
+ (to `/data` because that is where downloads and updates go).
53
+
54
+ ### docker run
55
+
56
+ ```sh
57
+ mkdir -p zims state # create them yourself, or Docker creates them as root
58
+ docker run -d --name zim-pro \
59
+ --restart unless-stopped \
60
+ --user "$(id -u):$(id -g)" \
61
+ -p 8083:8080 \
62
+ -v "$PWD/zims:/data" \
63
+ -v "$PWD/state:/state" \
64
+ --add-host host.docker.internal:host-gateway \
65
+ ghcr.io/magicstack-llp/zim-pro:latest
66
+ ```
67
+
68
+ Then open http://localhost:8083. `--add-host` is only needed for Ask AI to reach a model
69
+ server on the same machine (Ollama, LM Studio, llama.cpp).
70
+
71
+ ### docker compose
72
+
73
+ ```yaml
74
+ services:
75
+ zim-pro:
76
+ image: ghcr.io/magicstack-llp/zim-pro:latest
77
+ container_name: zim-pro
78
+ restart: unless-stopped
79
+ user: "1000:1000" # your own uid:gid, from `id -u` and `id -g`
80
+ ports:
81
+ - "8083:8080"
82
+ extra_hosts:
83
+ - "host.docker.internal:host-gateway"
84
+ volumes:
85
+ - ./zims:/data # your folder of .zim files
86
+ - ./state:/state
87
+ # environment:
88
+ # KIWIX_ADMIN_TOKEN: change-me # required for changes and /mcp when set
89
+ ```
90
+
91
+ ```sh
92
+ mkdir -p zims state
93
+ docker compose up -d
94
+ ```
95
+
96
+ ### Everyday commands
97
+
98
+ | To | docker compose | docker run |
99
+ |---|---|---|
100
+ | See the logs | `docker compose logs -f` | `docker logs -f zim-pro` |
101
+ | Stop | `docker compose stop` | `docker stop zim-pro` |
102
+ | Start again | `docker compose start` | `docker start zim-pro` |
103
+ | Update to the newest image | `docker compose pull && docker compose up -d` | `docker pull ghcr.io/magicstack-llp/zim-pro:latest`, then `docker rm -f zim-pro` and the `docker run` above |
104
+ | Remove the container | `docker compose down` | `docker rm -f zim-pro` |
105
+
106
+ Removing the container loses nothing: the books are in `zims` and the settings, chats and
107
+ download progress are in `state`. A download in progress when the container stops carries on
108
+ from where it was when it starts again.
109
+
110
+ To stay on one version, use a version tag in place of `latest`, such as `:0.2.1` or `:0.2`.
111
+
112
+ ### Build the image yourself
113
+
114
+ The `compose.yml` in this repository builds from source:
115
+
116
+ ```sh
117
+ mkdir -p state
118
+ docker compose up -d --build # http://localhost:8083
119
+ ```
120
+
121
+ It mounts `/mnt/storage/Media/Kiwix/data` as the library; change that path to your own
122
+ folder of ZIM files.
123
+
124
+ ## Run without Docker
125
+
126
+ Each [release](https://github.com/magicstack-llp/zim-pro/releases) has two other ways to run
127
+ it, with the web app included in both.
128
+
129
+ **Standalone executable** for Linux (x86-64 and arm64). One file, no Python needed:
130
+
131
+ ```sh
132
+ tar -xzf zim-pro-0.2.1-linux-x86_64.tar.gz
133
+ cd zim-pro-0.2.1-linux-x86_64
134
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state ./zim-pro # http://localhost:8080
135
+ ```
136
+
137
+ **Python package**, for Python 3.13 on Linux or macOS:
138
+
139
+ ```sh
140
+ pip install zim-pro # or: uvx zim-pro
141
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state zim-pro # http://localhost:8080
142
+ ```
143
+
144
+ `SHA256SUMS` in the release lists the checksum of every file.
145
+
146
+ ## Run from source
147
+
148
+ ```sh
149
+ (cd web && npm ci && npm run build)
150
+ KIWIX_DATA_DIR=/path/to/zims KIWIX_STATE_DIR=./state uv run zim-pro
151
+ ```
152
+
153
+ For UI work, run `npm run dev` in `web/` (set `KIWIX_BACKEND` if the server is not on
154
+ `127.0.0.1:8080`).
155
+
156
+ ## Configuration
157
+
158
+ | Variable | Default | Meaning |
159
+ |---|---|---|
160
+ | `KIWIX_DATA_DIR` | `/data` | Folder of `.zim` files (scanned recursively) |
161
+ | `KIWIX_STATE_DIR` | `/state` | Where the SQLite database lives |
162
+ | `KIWIX_HOST` / `KIWIX_PORT` | `0.0.0.0` / `8080` | Listen address |
163
+ | `KIWIX_ADMIN_TOKEN` | unset | If set, changes and `/mcp` need `Authorization: Bearer <token>` |
164
+ | `KIWIX_CATALOG_URL` | Kiwix OPDS feed | Catalog to check for updates |
165
+
166
+ Download limits, the update schedule and policies are set in the web UI under Settings.
167
+
168
+ The project was first called Kiwix Updater. The `KIWIX_*` variables and the database file
169
+ (`kiwix-updater.sqlite3`) keep that name so existing installs carry on unchanged, and the
170
+ `kiwix-updater` command still works alongside `zim-pro`.
171
+
172
+ ## Ask AI
173
+
174
+ Add one or more providers under Settings → AI providers, then pick a default model. Any
175
+ server with the OpenAI `/chat/completions` API works: OpenAI, Google Gemini, Perplexity,
176
+ OpenRouter, Groq, Ollama, LM Studio, llama.cpp, vLLM. The model can be switched per message
177
+ in the chat box.
178
+
179
+ Each provider has a switch: turned off, it is kept with its key but not used or offered.
180
+
181
+ Two providers that need no account or key are added on first start, so Ask AI works straight
182
+ after installing: **OVHcloud AI Endpoints** (Llama 3.3 70B, Qwen, Mistral, gpt-oss; about two
183
+ requests a minute per model) and **Kilo Gateway**'s free models (about 200 requests an hour;
184
+ Kilo may log free-tier prompts for training). Both are rate-limited per IP address and can be
185
+ removed under Settings; a removed one is not added back.
186
+
187
+ For a model server on the Docker host, use `http://host.docker.internal:<port>/v1` (Ollama:
188
+ `11434`, LM Studio: `1234`), not `localhost`, and make the server listen on all interfaces
189
+ (for Ollama, `OLLAMA_HOST=0.0.0.0`).
190
+
191
+ How a question is answered (see `src/zim_pro/retrieval.py`):
192
+
193
+ 1. **Plan.** The model is shown the list of books and returns several short queries from
194
+ different angles, extra keywords a good article would contain, and the books most likely
195
+ to help. If it cannot be asked in time, the question's own words are used.
196
+ 2. **Search the relevant books in depth** with every query and keyword. These are the model's
197
+ picks, books named after the subject, and always the main encyclopedia.
198
+ 3. **Search every other book lightly** with the main queries.
199
+ 4. **Rank.** Articles found by several searches rise (reciprocal rank fusion); the best 24 are
200
+ read and scored on how much of the subject one passage covers, and only passages that fit
201
+ are sent to the model with the question.
202
+
203
+ Articles reach the model as Markdown (`library/article.py`), not HTML: navigation, pictures
204
+ and their captions, citation markers, reference lists, "See also" and "External links"
205
+ sections and licence footers are removed and links are flattened to their text, so the tokens
206
+ go on prose. Each passage is sent under the heading of the section it came from.
207
+
208
+ The pictures, videos and recordings on each source's page are listed with the source and
209
+ shown as thumbnails under it; they are not sent to the model.
210
+
211
+ Chats are stored in the database.
212
+
213
+ Every prompt carries formatting rules, and answers are passed through a repair step before
214
+ they are shown (unclosed code blocks, leaked reasoning, malformed lists, tables and citations),
215
+ since small models often get Markdown wrong. Your own standing instructions can be added
216
+ under Settings → Ask AI.
217
+
218
+ **Web search** (off by default) is used when the library has no good match, or for every
219
+ question while the Internet button in the chat box is on. Either:
220
+
221
+ - a *search service* (Tavily, Brave, Serper for Google results, or your own SearXNG), whose
222
+ results are added to the sources for your chat model, or
223
+ - a *searching model* that looks things up itself: Perplexity Sonar, OpenRouter's web add-on,
224
+ an OpenAI search model, or Gemini with Google Search.
225
+
226
+ **Read aloud** adds a listen button to answers, using the browser's built-in voice or a
227
+ provider's speech model (OpenAI, Gemini, Groq, or a local `/audio/speech` server).
228
+
229
+ ## MCP
230
+
231
+ ```sh
232
+ claude mcp add --transport http zim-pro http://localhost:8083/mcp
233
+ ```
234
+
235
+ Tools: `list_books`, `search`, `suggest`, `get_article`, `get_article_outline`,
236
+ `list_updates`, and `start_update` (off until enabled in Settings).
237
+
238
+ For a stdio client, run `zim-pro mcp` with `KIWIX_DATA_DIR` and `KIWIX_STATE_DIR` set.
239
+
240
+ ## Notes
241
+
242
+ - Articles are served from the same origin as the admin UI, as with `kiwix-serve`. Scripts
243
+ inside a ZIM could therefore call the API; set `KIWIX_ADMIN_TOKEN` if you load ZIMs you do
244
+ not trust or expose the port beyond your LAN.
245
+ - libzim holds Python's GIL, so a slow full-text search briefly delays other requests.
246
+ - libzim does not expose relevance scores, so cross-book search groups results by book
247
+ instead of ranking them together.
248
+
249
+ ## Tests
250
+
251
+ ```sh
252
+ uv run pytest
253
+ (cd web && npm test)
254
+ ```
255
+
256
+ ## Built with
257
+
258
+ Python 3.13, FastAPI and [python-libzim](https://github.com/openzim/python-libzim) on the
259
+ server, SQLite for state, and React, Vite and Tailwind for the web app. Zim Pro is an
260
+ independent project and is not affiliated with Kiwix.
261
+
262
+ ## Releasing
263
+
264
+ Pushing a tag of the form `v1.2.3` runs `.github/workflows/release.yml`:
265
+
266
+ 1. Lint and tests. Nothing is published if they fail.
267
+ 2. In parallel: the Docker image for amd64 and for arm64 (each on its own runner, then joined
268
+ and published to GHCR as `1.2.3`, `1.2` and `latest`), the Python wheel and source
269
+ archive (then uploaded to PyPI), and the standalone executables.
270
+ 3. Last, and only if all of those succeeded, the GitHub release, with the executables, the
271
+ wheel, the source archive and their checksums attached.
272
+
273
+ The package version comes from the tag, and the release notes from that version's section of
274
+ `CHANGELOG.md`, so move the entries under "Unreleased" to a new version heading before
275
+ tagging.
276
+
277
+ ```sh
278
+ git tag v0.2.1 && git push origin v0.2.1
279
+ ```
280
+
281
+ Every push to `master` and every pull request runs `.github/workflows/ci.yml`: Python lint
282
+ (`ruff check`), formatting check and tests, and the web app's type check, tests and build.
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "zim-pro"
3
+ version = "0.2.1"
4
+ description = "Offline AI knowledge base: read, search and ask your ZIM library (Wikipedia and the Kiwix catalog) with a local or hosted model, and serve it to LLM apps over MCP."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ keywords = [
8
+ "zim",
9
+ "kiwix",
10
+ "offline",
11
+ "wikipedia",
12
+ "mcp",
13
+ "rag",
14
+ "knowledge-base",
15
+ ]
16
+ dependencies = [
17
+ "fastapi>=0.143.0",
18
+ "httpx>=0.28.1",
19
+ "libzim>=3.13.1",
20
+ "markdownify>=1.2.3",
21
+ "mcp>=2.3.0",
22
+ "pydantic-settings>=2.15.0",
23
+ "selectolax>=1.0.0",
24
+ "uvicorn[standard]>=0.54.0",
25
+ ]
26
+
27
+ [[project.authors]]
28
+ name = "Kuldip Satpute"
29
+ email = "satputekuldip@gmail.com"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/magicstack-llp/zim-pro"
33
+ Repository = "https://github.com/magicstack-llp/zim-pro"
34
+ Changelog = "https://github.com/magicstack-llp/zim-pro/blob/master/CHANGELOG.md"
35
+ Issues = "https://github.com/magicstack-llp/zim-pro/issues"
36
+
37
+ [project.scripts]
38
+ zim-pro = "zim_pro:main"
39
+ kiwix-updater = "zim_pro:main"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.12.24,<0.13.0"]
43
+ build-backend = "uv_build"
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pytest>=9.1.1",
48
+ "pytest-asyncio>=1.4.0",
49
+ "ruff>=0.17.0",
50
+ ]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ src = ["src"]
55
+
56
+ [tool.ruff.lint]
57
+ select = [
58
+ "E",
59
+ "F",
60
+ "I",
61
+ "UP",
62
+ "B",
63
+ ]
64
+ ignore = ["B008"]
65
+
66
+ [tool.pytest.ini_options]
67
+ asyncio_mode = "auto"
68
+ testpaths = ["tests"]