zimlore 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.
- zimlore-0.1.0/.gitignore +49 -0
- zimlore-0.1.0/LICENSE +21 -0
- zimlore-0.1.0/NOTICE +21 -0
- zimlore-0.1.0/PKG-INFO +206 -0
- zimlore-0.1.0/PLAN.md +139 -0
- zimlore-0.1.0/README.md +174 -0
- zimlore-0.1.0/ROADMAP.md +143 -0
- zimlore-0.1.0/conftest.py +1 -0
- zimlore-0.1.0/pyproject.toml +107 -0
- zimlore-0.1.0/tests/conftest.py +5 -0
- zimlore-0.1.0/tests/test_catalog.py +429 -0
- zimlore-0.1.0/tests/test_catalog_hardening.py +369 -0
- zimlore-0.1.0/tests/test_cli_and_mcp.py +184 -0
- zimlore-0.1.0/tests/test_converter_structure.py +261 -0
- zimlore-0.1.0/tests/test_hardening.py +381 -0
- zimlore-0.1.0/tests/test_license.py +53 -0
- zimlore-0.1.0/tests/test_manage.py +355 -0
- zimlore-0.1.0/tests/test_manage_safety.py +436 -0
- zimlore-0.1.0/tests/test_markdown.py +215 -0
- zimlore-0.1.0/tests/test_packaging.py +41 -0
- zimlore-0.1.0/tests/test_refs_and_library.py +159 -0
- zimlore-0.1.0/tests/test_regions.py +381 -0
- zimlore-0.1.0/tests/test_roadmap_rows.py +100 -0
- zimlore-0.1.0/tests/test_service.py +541 -0
- zimlore-0.1.0/tests/test_snapshot.py +348 -0
- zimlore-0.1.0/tests/test_sync.py +419 -0
- zimlore-0.1.0/tests/test_usability_fixes.py +280 -0
- zimlore-0.1.0/tests/test_versions.py +380 -0
- zimlore-0.1.0/tests/zimfixture.py +286 -0
- zimlore-0.1.0/zimlore/__init__.py +5 -0
- zimlore-0.1.0/zimlore/__main__.py +8 -0
- zimlore-0.1.0/zimlore/_libzim.py +37 -0
- zimlore-0.1.0/zimlore/catalog.py +592 -0
- zimlore-0.1.0/zimlore/cli.py +618 -0
- zimlore-0.1.0/zimlore/errors.py +45 -0
- zimlore-0.1.0/zimlore/groups.py +30 -0
- zimlore-0.1.0/zimlore/library.py +648 -0
- zimlore-0.1.0/zimlore/manage.py +632 -0
- zimlore-0.1.0/zimlore/markdown.py +1247 -0
- zimlore-0.1.0/zimlore/mcp.py +183 -0
- zimlore-0.1.0/zimlore/purl.py +130 -0
- zimlore-0.1.0/zimlore/py.typed +0 -0
- zimlore-0.1.0/zimlore/refs.py +90 -0
- zimlore-0.1.0/zimlore/service.py +1126 -0
- zimlore-0.1.0/zimlore/snapshot.py +195 -0
- zimlore-0.1.0/zimlore/sync.py +269 -0
zimlore-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# The corpus is not the point of this repository and must never be in it.
|
|
2
|
+
#
|
|
3
|
+
# ZIM files are other people's content under their own licences (see NOTICE). They live in ZIMLORE_HOME
|
|
4
|
+
# (default ~/.zimlore), never in the checkout. A ZIM is a single file that can be gigabytes, and git history
|
|
5
|
+
# cannot walk it back, so every way of naming one is ignored from the first commit.
|
|
6
|
+
*.zim
|
|
7
|
+
*.zim.*
|
|
8
|
+
*.zimaa
|
|
9
|
+
*.meta4
|
|
10
|
+
*.idx.tmp/
|
|
11
|
+
zim/
|
|
12
|
+
zimlore-home/
|
|
13
|
+
.zimlore/
|
|
14
|
+
|
|
15
|
+
.venv/
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.pyc
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
dist/
|
|
22
|
+
build/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.coverage
|
|
25
|
+
.coverage.*
|
|
26
|
+
htmlcov/
|
|
27
|
+
|
|
28
|
+
# --- Captures and secrets ---
|
|
29
|
+
*.har
|
|
30
|
+
.env
|
|
31
|
+
.env.*
|
|
32
|
+
*.pem
|
|
33
|
+
*.key
|
|
34
|
+
|
|
35
|
+
# --- Scratch: the workspace pre-commit hook refuses these ---
|
|
36
|
+
probe_*.py
|
|
37
|
+
*.log
|
|
38
|
+
*.out
|
|
39
|
+
*.tmp
|
|
40
|
+
scratch/
|
|
41
|
+
tmp/
|
|
42
|
+
|
|
43
|
+
# --- Agent worktrees and local tool state ---
|
|
44
|
+
.claude/worktrees/
|
|
45
|
+
.claude/settings.local.json
|
|
46
|
+
.vscode/
|
|
47
|
+
.idea/
|
|
48
|
+
.DS_Store
|
|
49
|
+
Thumbs.db
|
zimlore-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Justin Capella
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without
|
|
7
|
+
limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
8
|
+
Software, and to permit persons to whom the Software is furnished to do so, subject to the following
|
|
9
|
+
conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
|
12
|
+
of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
15
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
17
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
18
|
+
DEALINGS IN THE SOFTWARE.
|
|
19
|
+
|
|
20
|
+
This licence covers the code in this repository. It does not cover any ITU-T Recommendation text, which is
|
|
21
|
+
copyright ITU and is not included here - see NOTICE.
|
zimlore-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
NOTICE
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
This repository contains no ZIM file and no text from any ZIM file.
|
|
5
|
+
|
|
6
|
+
A ZIM is an archive of someone else's content, and each one carries that content's own licence: DevDocs ZIMs
|
|
7
|
+
archive upstream project documentation under whatever licence each project chose, Stack Exchange ZIMs are
|
|
8
|
+
user-contributed content under Creative Commons terms with attribution requirements, Wikipedia is another
|
|
9
|
+
case again. Those licences differ from each other and from this software's. zimlore therefore never ships,
|
|
10
|
+
vendors or embeds ZIM content: files live in `ZIMLORE_HOME` (default `~/.zimlore`, subdirectory `zim/`) on
|
|
11
|
+
each user's machine, fetched by the user with `zimlore get` or copied there, and `.gitignore` names every way
|
|
12
|
+
a ZIM can be spelled so that one cannot be staged by accident. `tests/test_license.py` is a tripwire against
|
|
13
|
+
the obvious copy, not a proof against a determined one; its test ZIMs are built from invented text at test
|
|
14
|
+
time, in a temporary directory.
|
|
15
|
+
|
|
16
|
+
What zimlore says about a document is a citation (library, ZIM edition date, entry path) plus the licence
|
|
17
|
+
metadata the ZIM itself carries in its `License` field. Many ZIMs, including the DevDocs ones measured while
|
|
18
|
+
writing this, carry none, and zimlore reports that as "not stated in the ZIM" rather than guessing: the
|
|
19
|
+
licence of the text you reuse is the upstream project's, and you should read it there.
|
|
20
|
+
|
|
21
|
+
The code in this repository is licensed under the MIT licence in LICENSE. That licence covers the code only.
|
zimlore-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zimlore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Kiwix library (ZIM files) served to AI agents over MCP: offline, citable, token-budgeted documentation search and reading. You supply the ZIMs; none are shipped.
|
|
5
|
+
Project-URL: Homepage, https://github.com/b1tninja/zimlore
|
|
6
|
+
Project-URL: Source, https://github.com/b1tninja/zimlore
|
|
7
|
+
Project-URL: Issues, https://github.com/b1tninja/zimlore/issues
|
|
8
|
+
Author-email: Justin Capella <justincapella@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: context7,devdocs,documentation,kiwix,libzim,mcp,model-context-protocol,offline,openzim,zim
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
23
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: libzim>=4.1; sys_platform != 'win32' or platform_machine != 'ARM64'
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: mcp>=2.3; extra == 'all'
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: mcp>=2.3; extra == 'mcp'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# zimlore
|
|
34
|
+
|
|
35
|
+
A [Kiwix](https://kiwix.org) library (ZIM files) served to AI agents over MCP: **offline, citable,
|
|
36
|
+
token-budgeted documentation**. It is the offline counterpart of [Context7](https://github.com/upstash/context7):
|
|
37
|
+
instead of asking a hosted service for snippets, an agent searches and reads ZIM files you hold on disk, and
|
|
38
|
+
every answer carries the ZIM's name, edition date and entry path.
|
|
39
|
+
|
|
40
|
+
You supply the ZIMs. **None are shipped**, and `.gitignore` names every way one could be staged
|
|
41
|
+
(see [NOTICE](NOTICE)).
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install "zimlore[mcp]"
|
|
45
|
+
zimlore catalog flask # search the public Kiwix catalogue
|
|
46
|
+
zimlore get devdocs_en_flask # download it, verified by SHA-256, into ~/.zimlore/zim
|
|
47
|
+
zimlore search "application factory" -l flask
|
|
48
|
+
zimlore read flask§patterns/appfactories/index
|
|
49
|
+
zimlore mcp # the MCP server, on stdio
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Managing what is installed (none of these touch the network):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
zimlore du # disk use per library
|
|
56
|
+
zimlore verify [--adopt] # check files against their provenance record and libzim's checksum
|
|
57
|
+
zimlore remove flask --yes # delete (dry run without --yes; --edition DATE or --all when several)
|
|
58
|
+
zimlore prune --keep 2 --yes # drop old zimlore-installed editions and stale partial downloads
|
|
59
|
+
# (--include-unmanaged also deletes files zimlore did not install)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Putting ZIMs in place (`add` is local; `sync` and `get` use the network and resume an interrupted download):
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
zimlore add some.zim [--link] # copy (or link) a local ZIM in after libzim opens it and its checksum holds
|
|
66
|
+
zimlore sync --group web-testing --dry-run # plan against a manifest or a named group; sizes are approximate
|
|
67
|
+
zimlore sync --manifest .zimlore.toml [--update] # install what is missing; --update stages newer editions
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A manifest (`./.zimlore.toml`, else `$ZIMLORE_HOME/libraries.toml`) names catalogue entries only, never URLs or
|
|
71
|
+
paths, so it can be committed: `libraries = ["devdocs_en_python"]`, `groups = ["infra"]`, `max_age_days = 365`,
|
|
72
|
+
`budget = "2GB"` (the library may not grow past it). `sync` never removes anything. The shipped groups
|
|
73
|
+
(`languages-core`, `web-testing`, `infra`) are an editorial default awaiting the maintainer's decision.
|
|
74
|
+
|
|
75
|
+
Every download records a sidecar (`<file>.zimlore.json`: source URL, verified SHA-256, time). A file without one
|
|
76
|
+
reports `provenance: unrecorded` in `zim_libraries`, `zim_resolve` and `zim_cite`; it is never presented as official.
|
|
77
|
+
|
|
78
|
+
Register it with a client (this edits your client's configuration, so it is yours to run):
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
claude mcp add --scope user zimlore -- <venv>\Scripts\zimlore.exe mcp
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Installing documentation, by hand or by an agent with a shell
|
|
85
|
+
|
|
86
|
+
Any agent that can run a command can install documentation; nothing needs the MCP server for that. Every
|
|
87
|
+
missing-library error already names the command. The Kiwix catalogue is searchable (`q`, `lang`, `tag`,
|
|
88
|
+
`maxsize`; the OPDS description lists them), so:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
zimlore catalog playwright # search the live catalogue
|
|
92
|
+
zimlore catalog --tag devdocs --max-mb 1 # every DevDocs ZIM of at most 1 MB (140 on 2026-11-07)
|
|
93
|
+
zimlore available flask # the same, offline, from a cached snapshot (catalog --refresh)
|
|
94
|
+
zimlore get devdocs_en_flask # download, verified against the .meta4 SHA-256
|
|
95
|
+
zimlore add ~/Downloads/some.zim # a ZIM you already have: opened, checksum-checked, recorded
|
|
96
|
+
zimlore sync --group languages-core --dry-run # plan a whole set first; drop --dry-run to install
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Each install writes a provenance record, so `zim_cite` can say where a file came from. Installing never needs
|
|
100
|
+
the MCP server, and the MCP server never installs: planned on-demand installation from a package URL
|
|
101
|
+
(`pkg:pypi/flask@3.1.0` to that version's official docs) is designed in `docs/on-demand-docs.md`, not built.
|
|
102
|
+
|
|
103
|
+
## Where the files live
|
|
104
|
+
|
|
105
|
+
`ZIMLORE_HOME` (default `~/.zimlore`), subdirectory `zim/`. Set `ZIMLORE_ZIM_PATH` (a list separated by `;` on
|
|
106
|
+
Windows, `:` elsewhere) to read an existing Kiwix library where it already is. Nothing is ever written inside
|
|
107
|
+
the repository. An optional `$ZIMLORE_HOME/aliases.json` maps your own names: `{"py": "devdocs_en_python"}`.
|
|
108
|
+
|
|
109
|
+
## The tools
|
|
110
|
+
|
|
111
|
+
| Tool | Does |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `zim_libraries` | installed libraries with edition date, size, search type, `freshness`; ranks them against a name |
|
|
114
|
+
| `zim_resolve` | one name or alias (`flask`, `python`, `security.stackexchange.com`) to exactly one library; an ambiguous name returns the candidates |
|
|
115
|
+
| `zim_search` | one library or all of them; hits are refs with an excerpt and a citation |
|
|
116
|
+
| `zim_read` | an article, a section (`#anchor`), an API definition, or one block (`¶n`) as markdown or text, paged by a token budget |
|
|
117
|
+
| `zim_outline` | headings and API definitions with anchors, or a page of a library's articles |
|
|
118
|
+
| `zim_links` | links out, internal ones resolved to refs and marked `in_zim` |
|
|
119
|
+
| `zim_cite` | the citation string, edition, freshness and licence note |
|
|
120
|
+
| `zim_available` | **offline** search of the cached catalogue snapshot: what could be installed, the exact `zimlore get <name>` command, and the snapshot's age |
|
|
121
|
+
|
|
122
|
+
One reference grammar, accepted everywhere: `devdocs_en_flask§blueprints/index#why-blueprints¶3`. The library
|
|
123
|
+
may be any alias (`flask§…`), `::` is the ASCII spelling of `§`, and a bare path works when `library` is given.
|
|
124
|
+
`¶n` counts blocks of *our conversion* of the article, so the entry path and anchor are the durable part.
|
|
125
|
+
|
|
126
|
+
Conventions inherited from `rfclib` and `itulib`:
|
|
127
|
+
|
|
128
|
+
- A missing or empty library is an **`error` object that names the fix**, never an empty list. `hits: []` means
|
|
129
|
+
the search matched nothing, not that the topic is undocumented.
|
|
130
|
+
- Long reads end with `next` (`{"ref", "start"}`); pass it back. A page always makes progress, even when one code
|
|
131
|
+
block alone exceeds the budget (it is returned whole and flagged `oversize`).
|
|
132
|
+
- Every response states `freshness` (`date`, `age_days`, `stale`). A ZIM is a snapshot, and one with no `Date`
|
|
133
|
+
metadata reports `stale: null`, never "fresh".
|
|
134
|
+
- HTML becomes markdown with **code blocks (and their language), tables, headings, lists and definition lists
|
|
135
|
+
kept**; scripts, navigation chrome and inline base64 images are dropped (images appear as `[image]`).
|
|
136
|
+
|
|
137
|
+
## Offline catalogue
|
|
138
|
+
|
|
139
|
+
`zimlore catalog --refresh` (the one command here that uses the network, besides `catalog QUERY`, `get` and
|
|
140
|
+
`outdated`) stores the English Kiwix catalogue in `$ZIMLORE_HOME/catalog.json`, about 0.4 MB for 1,309 entries.
|
|
141
|
+
After that `zimlore available flask` and `zimlore catalog --offline [flask]` say what could be installed and the
|
|
142
|
+
exact `zimlore get <name>` to run, with no network and with the snapshot's age. The MCP server reads the same
|
|
143
|
+
file: `zim_available`, and a `zim_resolve`/`zim_search` miss carries `obtainable` (the top five), so an agent can
|
|
144
|
+
tell the user what to install instead of concluding a topic is undocumented. Without a snapshot the answer is an
|
|
145
|
+
error naming `zimlore catalog --refresh`; a damaged snapshot is an error too, never an empty list.
|
|
146
|
+
|
|
147
|
+
## Against Context7
|
|
148
|
+
|
|
149
|
+
| | Context7 | zimlore |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| Where it runs | hosted MCP server `mcp.context7.com`; the MCP server is MIT, the crawler and API are private | your machine; no network after the download |
|
|
152
|
+
| Rate limits | yes; unkeyed requests get low limits, a free key raises them | none |
|
|
153
|
+
| Privacy | queries go to a third party | nothing leaves the machine |
|
|
154
|
+
| Library names | `resolve-library-id` → `/owner/repo[/version]` | `zim_resolve` → a ZIM `Name` plus its edition date |
|
|
155
|
+
| Versions | pin `/owner/repo/vX.Y.Z`; falls back to the nearest indexed | **pinned by ZIM date**; a ZIM does not record the upstream release number, the pages do |
|
|
156
|
+
| Freshness | continuously re-crawled by the service | as fresh as the ZIM you hold (`zimlore outdated` compares with the catalogue) |
|
|
157
|
+
| Coverage | very large and growing; any GitHub repo can be added | what Kiwix publishes: see below |
|
|
158
|
+
| Result shape | curated code and info snippets ranked by the service | whole pages, or one section/definition, as markdown, cut to your budget |
|
|
159
|
+
| Ranking | service-side, semantic, tuned | Xapian keyword search; **no semantic matching** (see Limits) |
|
|
160
|
+
| Citations | library id | ZIM name, edition date, entry path, anchor |
|
|
161
|
+
|
|
162
|
+
Pick Context7 when you need the newest release of a fast-moving library and are online. Pick zimlore when you
|
|
163
|
+
are offline, behind a policy that forbids sending queries out, want a reproducible snapshot, or want the same
|
|
164
|
+
documents every run. They are not exclusive.
|
|
165
|
+
|
|
166
|
+
## What Kiwix has for developers
|
|
167
|
+
|
|
168
|
+
Measured against the live catalogue on 2026-11-07 (`docs/research.md` has the method):
|
|
169
|
+
|
|
170
|
+
- **243 DevDocs ZIMs**, 0.67 GB in total (median 0.85 MB, largest scikit-learn at 58 MB), dated 2026-05-04 to
|
|
171
|
+
2026-10-31: Python, Rust, Go, Node, React, Vue, TypeScript, Django, Flask, FastAPI, PostgreSQL, Docker,
|
|
172
|
+
Kubernetes, Terraform and more. This is the Context7-shaped part, and it is tiny.
|
|
173
|
+
- `docs.python.org_en_all` (3.96 GB, 2026-08-28), `peps.python_en_all`, `devhints.io_en_all`, `archlinux_en_all`,
|
|
174
|
+
`gentoo_en_all`, `www.mankier.com_en_all` (man pages), `php.net_en_all` (**2024-08-13**, old).
|
|
175
|
+
- **Stack Exchange**: 181 English-filter ZIMs, 191 GB in total; `stackoverflow.com_en_all` alone is 114.9 GB
|
|
176
|
+
(2026-07-02).
|
|
177
|
+
|
|
178
|
+
## Limits
|
|
179
|
+
|
|
180
|
+
- **Keyword search, not semantic.** On 34 questions paraphrased from page titles (`scripts/eval_retrieval.py`,
|
|
181
|
+
DevDocs Flask and Python), the right page was first for **10/34**, in the top 5 for **23/34**, MRR 0.436.
|
|
182
|
+
Questions that share words with the page do well; "isolated environment for project packages" does not find
|
|
183
|
+
"Virtual Environments and Packages". Searching with the library's own vocabulary works much better. With no
|
|
184
|
+
library given, 1 of 4 questions found its page: results from different ZIMs are interleaved because libzim
|
|
185
|
+
exposes no comparable score.
|
|
186
|
+
- **No upstream version.** The ZIM date says when the docs were scraped; which release they document is on the
|
|
187
|
+
pages.
|
|
188
|
+
- **Licences are not in the ZIMs we measured.** Neither DevDocs ZIM had a `License` metadata key; `zim_cite`
|
|
189
|
+
says "not stated in the ZIM" rather than guessing. DevDocs itself is MPL-2.0, but the text it archives is
|
|
190
|
+
each upstream project's.
|
|
191
|
+
- **`libzim` is GPL-3.0-or-later** (the binding; the C++ library is GPL-2.0 per its repository). zimlore is MIT
|
|
192
|
+
and imports it at run time; see `PLAN.md` for what that means for redistribution.
|
|
193
|
+
- Wheels exist for Windows x64, macOS and Linux (glibc and musl) on CPython 3.11+. On Windows ARM64 `pip
|
|
194
|
+
install zimlore` succeeds without `libzim`, and every tool reports the missing binding and the remedy.
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
python -m pip install -e . --group dev # pip 25.1+
|
|
200
|
+
python -m pytest -q # no ZIM needed: tests build a synthetic one with libzim's writer
|
|
201
|
+
python -m ruff check . ; python -m ruff format --check . ; python -m mypy zimlore tests
|
|
202
|
+
python scripts/eval_retrieval.py # needs devdocs_en_flask and devdocs_en_python installed
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
[ROADMAP.md](ROADMAP.md) lists every obligation with the test that would fail if it regressed;
|
|
206
|
+
[PLAN.md](PLAN.md) records the design decisions; [docs/research.md](docs/research.md) the sources.
|
zimlore-0.1.0/PLAN.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# zimlore plan
|
|
2
|
+
|
|
3
|
+
zimlore serves a Kiwix library (ZIM files) to AI agents over MCP as an offline-first alternative to Context7.
|
|
4
|
+
This file records the decisions and why. Facts behind them are in [docs/research.md](docs/research.md); status
|
|
5
|
+
of each obligation is in [ROADMAP.md](ROADMAP.md).
|
|
6
|
+
|
|
7
|
+
## 1. Scope
|
|
8
|
+
|
|
9
|
+
In: find a library by name, search it, read an article/section/definition/block as markdown or text under a token
|
|
10
|
+
budget, follow links, cite with the ZIM's edition date, say how stale a snapshot is, and fetch ZIMs from the
|
|
11
|
+
public catalogue with a verified hash.
|
|
12
|
+
|
|
13
|
+
Out, on purpose:
|
|
14
|
+
|
|
15
|
+
- **Shipping or caching ZIM content.** Files live in `ZIMLORE_HOME`, never in the repository.
|
|
16
|
+
- **Semantic retrieval.** libzim gives Xapian keyword search and a title index. An embedding index would be a
|
|
17
|
+
second corpus to build and keep in step with each ZIM; if it is wanted it is a separate extra, not a promise
|
|
18
|
+
made here (ROADMAP `zl.semantic_search`, `[ ]`).
|
|
19
|
+
- **Re-hosting upstream documentation as curated snippets.** Context7's value is curation; zimlore returns the
|
|
20
|
+
page, section or definition as published.
|
|
21
|
+
- **Replacing kiwix-serve.** It is a library for agents, not a web server for people.
|
|
22
|
+
|
|
23
|
+
## 2. Decisions
|
|
24
|
+
|
|
25
|
+
**Name: `zimlore`.** Free on PyPI (`names.py`: simple 404, json 404) with *zero* one-keystroke neighbours in the
|
|
26
|
+
931,439-name snapshot, free as a GitHub repository name under `b1tninja` (404), and not in the workspace. Rejected:
|
|
27
|
+
`zimlib` (ten live neighbours incl. `ziplib`, `zilib`), `zimdoc`/`zimdocs` (one keystroke from `simdoc`/`vimdoc`
|
|
28
|
+
and from each other), `docsmith` (taken), `zimdex` (neighbours `zindex`, `imdex`). Sibling pattern is
|
|
29
|
+
`rfclib`/`itulib`; `lib` would have been natural but every `zim*lib` form collides.
|
|
30
|
+
|
|
31
|
+
**One module owns the native import (`_libzim.py`).** `libzim` is a compiled extension with wheels for Windows
|
|
32
|
+
x64, macOS, Linux (glibc, musl) and no Windows ARM64 wheel. The dependency carries a marker that excludes only
|
|
33
|
+
Windows-on-ARM, so installation succeeds there and every tool answers with the missing-binding remedy instead of
|
|
34
|
+
a compiler error. Everything that does not read a ZIM (CLI `info`, the reference grammar, the converter, the
|
|
35
|
+
catalogue client) works without it. This is the `rfclib[zim]` pattern inverted: rfclib writes ZIMs and makes
|
|
36
|
+
libzim optional; zimlore *is* a ZIM reader, so it is a hard dependency.
|
|
37
|
+
|
|
38
|
+
**HTML to markdown with the standard library only.** `html.parser`, no BeautifulSoup/lxml/markdownify. The
|
|
39
|
+
measured inputs (DevDocs) are regular, and a hand-written converter is the only way to guarantee the three
|
|
40
|
+
things an agent needs and generic converters lose: code blocks with their language, anchors on headings and API
|
|
41
|
+
definitions (so `#anchor` reads work), and the removal of inline base64 images that would cost thousands of
|
|
42
|
+
tokens each. Cost: Stack Exchange and Wikipedia HTML has not been exercised (ROADMAP `zl.html_other_scrapers`).
|
|
43
|
+
|
|
44
|
+
**The unit of paging is the block.** A paragraph, heading, list item, table, definition or code fence is never
|
|
45
|
+
split. A page always contains at least one block, so a single oversized block (a 400-line log) is returned whole
|
|
46
|
+
and flagged rather than cut, because a cursor that cannot advance is worse than a long page. `¶n` is the nth
|
|
47
|
+
block; it indexes zimlore's conversion, so it is documented as less durable than the entry path and anchor.
|
|
48
|
+
|
|
49
|
+
**Sections and definitions are first-class.** DevDocs pages are whole chapters (the `os` page is the whole module).
|
|
50
|
+
`#anchor` reads one `<section id>` or one `<dt id>` definition and its body, which is what makes "just
|
|
51
|
+
`os.getcwd`" cost 40 tokens instead of 20,000.
|
|
52
|
+
|
|
53
|
+
**Library identity is the ZIM `Name`, version is the ZIM `Date`.** Aliases are derived (DevDocs slug, title
|
|
54
|
+
without "Docs", Stack Exchange host, user `aliases.json`) and *ranked*: exact > all words > prefix > substring >
|
|
55
|
+
fuzzy. A tie is an error listing the candidates, never a coin flip. Two editions of one `Name` resolve to the
|
|
56
|
+
newest and the response lists the others. The response says outright that a ZIM does not record the upstream
|
|
57
|
+
release (Context7's `/org/repo/vX.Y.Z` has no equivalent), because the honest alternative to a version number is
|
|
58
|
+
a date.
|
|
59
|
+
|
|
60
|
+
**Versions are not editions; a library has a family.** Producers that build documentation per package version
|
|
61
|
+
name each build differently (`golang_github.com_spf13_cobra_v1.8.1`, `pypi_cryptography_50.0.2`), so by the rule
|
|
62
|
+
above each version is its own library, and measured on the Go prototype that made `cobra` resolve to v1.8.1 over
|
|
63
|
+
v1.10.2 (the alias is one character shorter and the ambiguity guard compared only scores). A `Book` therefore has
|
|
64
|
+
a derived `family` and `version`, with no change to the ZIM format or any stored state. The family is the `purl:`
|
|
65
|
+
tag without its version when the ZIM carries one (the producer's own statement), else the `Name` without a
|
|
66
|
+
trailing `_v?<digits>(.<alnum>)*(-<alnum>)?` token; a `_YYYY-MM` date suffix is an edition marker and is dropped
|
|
67
|
+
first, and a bare integer of more than two digits or a four-digit year (`wikipedia_en_100`, `ubuntu_en_all_2024`)
|
|
68
|
+
is a count or a date, not a release. A name with no such token is its own family with no version, which is every
|
|
69
|
+
DevDocs and Stack Exchange ZIM, so nothing changes for them. Resolution scores a *family* (the best of its members,
|
|
70
|
+
so versions of one library never tie each other and alias length cannot pick a version), then picks inside it:
|
|
71
|
+
no version asked means the newest by `library.version_key` (numeric components, `v` ignored, pre-releases below
|
|
72
|
+
releases, a version with no number last); a version asked (`cobra v1.8.1`, `cobra==1.8.1`, `cobra@1.8`, a purl's
|
|
73
|
+
`@version`) means that exact string, else the newest whose components start with it (`3.1` never matches `3.10`),
|
|
74
|
+
reported as `exact` or `prefix`; no match is an error listing the installed versions, and it names no build
|
|
75
|
+
command because none exists. A whole `Name` always selects that very ZIM. A purl is accepted wherever a library
|
|
76
|
+
name is (`zimlore/purl.py` reads nine types and refuses the rest): it matches a ZIM's `purl:` tag on type,
|
|
77
|
+
namespace and name, and falls back to the package name for ZIMs that carry no tag. Deleting stays per `Name`:
|
|
78
|
+
`prune` keeps the newest N *editions* of each Name and never counts another version as one, and `remove` of a
|
|
79
|
+
package with several versions refuses until a version or `--all-versions` is given. The cost accepted: a ZIM with
|
|
80
|
+
a purl tag and one without are different families even for one package.
|
|
81
|
+
|
|
82
|
+
**Staleness is data.** Every response carries `freshness {date, age_days, stale, stale_after_days}`. `stale` is
|
|
83
|
+
`age > 365` days, a stated convention; `null` when the ZIM has no date. `zimlore outdated` compares with the
|
|
84
|
+
catalogue (network).
|
|
85
|
+
|
|
86
|
+
**Search = title matches + full-text, with a documented fallback.** Exact-title hits first, then strict
|
|
87
|
+
full-text, then remaining title hits, then partial matches marked `some terms`. Partial matches come from
|
|
88
|
+
per-word searches fused by `ln(pages / pages matching)` weights and a title boost, because libzim's parser
|
|
89
|
+
requires every term (found by running it; first eval run scored hit@1 8/34). Across libraries, results are
|
|
90
|
+
interleaved rank by rank and the response says so, since libzim exposes no comparable score. The 34-case eval
|
|
91
|
+
(MRR 0.436, hit@5 23/34) is the honest summary: lexical, and weak on paraphrase.
|
|
92
|
+
|
|
93
|
+
**Citations are built, not typed.** `Title [Name], ZIM edition DATE, entry PATH#anchor`, plus the ZIM's `License`
|
|
94
|
+
metadata or the sentence "not stated in the ZIM". No ZIM we measured had a licence key, and inventing one from
|
|
95
|
+
the project name would be an unverifiable claim.
|
|
96
|
+
|
|
97
|
+
**Network is confined to `catalog.py`, and downloads are verified.** The hash comes from kiwix.org's `.meta4`;
|
|
98
|
+
bytes may come from any mirror (the `.zim` URL redirects to third-party hosts); a mismatch discards the file.
|
|
99
|
+
Only `https` (or `http` to localhost, for tests) is accepted. Nothing in the MCP server touches the network, which
|
|
100
|
+
is the point of the project.
|
|
101
|
+
|
|
102
|
+
**ZIM content is untrusted data.** A ZIM is third-party content: its article text, titles and metadata (`Title`,
|
|
103
|
+
`Name`, `Source`, and therefore the `cite` strings built from them) must be quoted and cited, never followed as
|
|
104
|
+
instructions. The MCP server instructions say so, and the read path is bounded against hostile files (an entry
|
|
105
|
+
size budget, a converter output ceiling, capped list indentation, bounded redirect chains).
|
|
106
|
+
|
|
107
|
+
**Licence of the code: MIT, with a flagged tension.** `libzim` (the binding) is GPL-3.0-or-later and zimlore
|
|
108
|
+
imports it. MIT code that imports a GPL library is fine to publish and to use, but a *combined* binary or image
|
|
109
|
+
that redistributes both is GPL-3.0 as a whole. The siblings (`rfclib[zim]`) take the same position. This is a
|
|
110
|
+
decision for the maintainer (section 5), not something this file can settle.
|
|
111
|
+
|
|
112
|
+
## 3. Verification model
|
|
113
|
+
|
|
114
|
+
Tests build a tiny synthetic ZIM with libzim's writer (invented text, DevDocs-shaped markup) so the suite needs
|
|
115
|
+
no download; `tests/test_license.py` is the tripwire against ZIM content entering the tree. What the suite cannot
|
|
116
|
+
prove (converter behaviour on real pages, ranking) is measured by `scripts/eval_retrieval.py` and by the live
|
|
117
|
+
MCP run recorded in `docs/research.md`, and ROADMAP rows that depend on them stay `[part]`.
|
|
118
|
+
|
|
119
|
+
## 4. Portfolio position
|
|
120
|
+
|
|
121
|
+
`PORTFOLIO.md` §4 defines "reference libraries - not tiered" as the corpora every protocol project *cites from*
|
|
122
|
+
(`rfclib`, `itulib`), justified "by a citation some other project could not make". zimlore is **not** that:
|
|
123
|
+
it is a developer-documentation corpus for agents, not a source for protocol citations. It is therefore not
|
|
124
|
+
sequenced in a wave and does not count against the two-project rule, but it also is not covered by the existing
|
|
125
|
+
sentence. A proposed entry is in [docs/portfolio-entry.md](docs/portfolio-entry.md); `PORTFOLIO.md` has **not**
|
|
126
|
+
been edited, because the workspace root is shared and the classification is the maintainer's call.
|
|
127
|
+
|
|
128
|
+
## 5. Decisions waiting for the maintainer
|
|
129
|
+
|
|
130
|
+
1. **Does `PORTFOLIO.md` list zimlore, and as what?** Recommendation: a third "reference library" row with the
|
|
131
|
+
sentence widened to "or that agents read from", no wave, no tier.
|
|
132
|
+
2. **Licence of zimlore** given GPL-3.0-or-later `libzim`. Recommendation: stay MIT (library use is the norm),
|
|
133
|
+
state it in NOTICE (done), and revisit if a bundled distribution is ever planned.
|
|
134
|
+
3. **Name `zimlore`**, and whether the GitHub repository is `b1tninja/zimlore`. Nothing has been pushed or created.
|
|
135
|
+
4. **Registering the MCP server** with Claude Code / Cline (`claude mcp add ...`): edits the user's configuration,
|
|
136
|
+
so not done.
|
|
137
|
+
5. **Publishing to PyPI**, and the release workflow's trusted-publisher setup. Not done.
|
|
138
|
+
6. **Which ZIMs to install by default** (a starter set). The DevDocs set is 0.67 GB in total and is the natural
|
|
139
|
+
candidate; Stack Overflow is 115 GB and is not.
|
zimlore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# zimlore
|
|
2
|
+
|
|
3
|
+
A [Kiwix](https://kiwix.org) library (ZIM files) served to AI agents over MCP: **offline, citable,
|
|
4
|
+
token-budgeted documentation**. It is the offline counterpart of [Context7](https://github.com/upstash/context7):
|
|
5
|
+
instead of asking a hosted service for snippets, an agent searches and reads ZIM files you hold on disk, and
|
|
6
|
+
every answer carries the ZIM's name, edition date and entry path.
|
|
7
|
+
|
|
8
|
+
You supply the ZIMs. **None are shipped**, and `.gitignore` names every way one could be staged
|
|
9
|
+
(see [NOTICE](NOTICE)).
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install "zimlore[mcp]"
|
|
13
|
+
zimlore catalog flask # search the public Kiwix catalogue
|
|
14
|
+
zimlore get devdocs_en_flask # download it, verified by SHA-256, into ~/.zimlore/zim
|
|
15
|
+
zimlore search "application factory" -l flask
|
|
16
|
+
zimlore read flask§patterns/appfactories/index
|
|
17
|
+
zimlore mcp # the MCP server, on stdio
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Managing what is installed (none of these touch the network):
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
zimlore du # disk use per library
|
|
24
|
+
zimlore verify [--adopt] # check files against their provenance record and libzim's checksum
|
|
25
|
+
zimlore remove flask --yes # delete (dry run without --yes; --edition DATE or --all when several)
|
|
26
|
+
zimlore prune --keep 2 --yes # drop old zimlore-installed editions and stale partial downloads
|
|
27
|
+
# (--include-unmanaged also deletes files zimlore did not install)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Putting ZIMs in place (`add` is local; `sync` and `get` use the network and resume an interrupted download):
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
zimlore add some.zim [--link] # copy (or link) a local ZIM in after libzim opens it and its checksum holds
|
|
34
|
+
zimlore sync --group web-testing --dry-run # plan against a manifest or a named group; sizes are approximate
|
|
35
|
+
zimlore sync --manifest .zimlore.toml [--update] # install what is missing; --update stages newer editions
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A manifest (`./.zimlore.toml`, else `$ZIMLORE_HOME/libraries.toml`) names catalogue entries only, never URLs or
|
|
39
|
+
paths, so it can be committed: `libraries = ["devdocs_en_python"]`, `groups = ["infra"]`, `max_age_days = 365`,
|
|
40
|
+
`budget = "2GB"` (the library may not grow past it). `sync` never removes anything. The shipped groups
|
|
41
|
+
(`languages-core`, `web-testing`, `infra`) are an editorial default awaiting the maintainer's decision.
|
|
42
|
+
|
|
43
|
+
Every download records a sidecar (`<file>.zimlore.json`: source URL, verified SHA-256, time). A file without one
|
|
44
|
+
reports `provenance: unrecorded` in `zim_libraries`, `zim_resolve` and `zim_cite`; it is never presented as official.
|
|
45
|
+
|
|
46
|
+
Register it with a client (this edits your client's configuration, so it is yours to run):
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
claude mcp add --scope user zimlore -- <venv>\Scripts\zimlore.exe mcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installing documentation, by hand or by an agent with a shell
|
|
53
|
+
|
|
54
|
+
Any agent that can run a command can install documentation; nothing needs the MCP server for that. Every
|
|
55
|
+
missing-library error already names the command. The Kiwix catalogue is searchable (`q`, `lang`, `tag`,
|
|
56
|
+
`maxsize`; the OPDS description lists them), so:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
zimlore catalog playwright # search the live catalogue
|
|
60
|
+
zimlore catalog --tag devdocs --max-mb 1 # every DevDocs ZIM of at most 1 MB (140 on 2026-11-07)
|
|
61
|
+
zimlore available flask # the same, offline, from a cached snapshot (catalog --refresh)
|
|
62
|
+
zimlore get devdocs_en_flask # download, verified against the .meta4 SHA-256
|
|
63
|
+
zimlore add ~/Downloads/some.zim # a ZIM you already have: opened, checksum-checked, recorded
|
|
64
|
+
zimlore sync --group languages-core --dry-run # plan a whole set first; drop --dry-run to install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each install writes a provenance record, so `zim_cite` can say where a file came from. Installing never needs
|
|
68
|
+
the MCP server, and the MCP server never installs: planned on-demand installation from a package URL
|
|
69
|
+
(`pkg:pypi/flask@3.1.0` to that version's official docs) is designed in `docs/on-demand-docs.md`, not built.
|
|
70
|
+
|
|
71
|
+
## Where the files live
|
|
72
|
+
|
|
73
|
+
`ZIMLORE_HOME` (default `~/.zimlore`), subdirectory `zim/`. Set `ZIMLORE_ZIM_PATH` (a list separated by `;` on
|
|
74
|
+
Windows, `:` elsewhere) to read an existing Kiwix library where it already is. Nothing is ever written inside
|
|
75
|
+
the repository. An optional `$ZIMLORE_HOME/aliases.json` maps your own names: `{"py": "devdocs_en_python"}`.
|
|
76
|
+
|
|
77
|
+
## The tools
|
|
78
|
+
|
|
79
|
+
| Tool | Does |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `zim_libraries` | installed libraries with edition date, size, search type, `freshness`; ranks them against a name |
|
|
82
|
+
| `zim_resolve` | one name or alias (`flask`, `python`, `security.stackexchange.com`) to exactly one library; an ambiguous name returns the candidates |
|
|
83
|
+
| `zim_search` | one library or all of them; hits are refs with an excerpt and a citation |
|
|
84
|
+
| `zim_read` | an article, a section (`#anchor`), an API definition, or one block (`¶n`) as markdown or text, paged by a token budget |
|
|
85
|
+
| `zim_outline` | headings and API definitions with anchors, or a page of a library's articles |
|
|
86
|
+
| `zim_links` | links out, internal ones resolved to refs and marked `in_zim` |
|
|
87
|
+
| `zim_cite` | the citation string, edition, freshness and licence note |
|
|
88
|
+
| `zim_available` | **offline** search of the cached catalogue snapshot: what could be installed, the exact `zimlore get <name>` command, and the snapshot's age |
|
|
89
|
+
|
|
90
|
+
One reference grammar, accepted everywhere: `devdocs_en_flask§blueprints/index#why-blueprints¶3`. The library
|
|
91
|
+
may be any alias (`flask§…`), `::` is the ASCII spelling of `§`, and a bare path works when `library` is given.
|
|
92
|
+
`¶n` counts blocks of *our conversion* of the article, so the entry path and anchor are the durable part.
|
|
93
|
+
|
|
94
|
+
Conventions inherited from `rfclib` and `itulib`:
|
|
95
|
+
|
|
96
|
+
- A missing or empty library is an **`error` object that names the fix**, never an empty list. `hits: []` means
|
|
97
|
+
the search matched nothing, not that the topic is undocumented.
|
|
98
|
+
- Long reads end with `next` (`{"ref", "start"}`); pass it back. A page always makes progress, even when one code
|
|
99
|
+
block alone exceeds the budget (it is returned whole and flagged `oversize`).
|
|
100
|
+
- Every response states `freshness` (`date`, `age_days`, `stale`). A ZIM is a snapshot, and one with no `Date`
|
|
101
|
+
metadata reports `stale: null`, never "fresh".
|
|
102
|
+
- HTML becomes markdown with **code blocks (and their language), tables, headings, lists and definition lists
|
|
103
|
+
kept**; scripts, navigation chrome and inline base64 images are dropped (images appear as `[image]`).
|
|
104
|
+
|
|
105
|
+
## Offline catalogue
|
|
106
|
+
|
|
107
|
+
`zimlore catalog --refresh` (the one command here that uses the network, besides `catalog QUERY`, `get` and
|
|
108
|
+
`outdated`) stores the English Kiwix catalogue in `$ZIMLORE_HOME/catalog.json`, about 0.4 MB for 1,309 entries.
|
|
109
|
+
After that `zimlore available flask` and `zimlore catalog --offline [flask]` say what could be installed and the
|
|
110
|
+
exact `zimlore get <name>` to run, with no network and with the snapshot's age. The MCP server reads the same
|
|
111
|
+
file: `zim_available`, and a `zim_resolve`/`zim_search` miss carries `obtainable` (the top five), so an agent can
|
|
112
|
+
tell the user what to install instead of concluding a topic is undocumented. Without a snapshot the answer is an
|
|
113
|
+
error naming `zimlore catalog --refresh`; a damaged snapshot is an error too, never an empty list.
|
|
114
|
+
|
|
115
|
+
## Against Context7
|
|
116
|
+
|
|
117
|
+
| | Context7 | zimlore |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| Where it runs | hosted MCP server `mcp.context7.com`; the MCP server is MIT, the crawler and API are private | your machine; no network after the download |
|
|
120
|
+
| Rate limits | yes; unkeyed requests get low limits, a free key raises them | none |
|
|
121
|
+
| Privacy | queries go to a third party | nothing leaves the machine |
|
|
122
|
+
| Library names | `resolve-library-id` → `/owner/repo[/version]` | `zim_resolve` → a ZIM `Name` plus its edition date |
|
|
123
|
+
| Versions | pin `/owner/repo/vX.Y.Z`; falls back to the nearest indexed | **pinned by ZIM date**; a ZIM does not record the upstream release number, the pages do |
|
|
124
|
+
| Freshness | continuously re-crawled by the service | as fresh as the ZIM you hold (`zimlore outdated` compares with the catalogue) |
|
|
125
|
+
| Coverage | very large and growing; any GitHub repo can be added | what Kiwix publishes: see below |
|
|
126
|
+
| Result shape | curated code and info snippets ranked by the service | whole pages, or one section/definition, as markdown, cut to your budget |
|
|
127
|
+
| Ranking | service-side, semantic, tuned | Xapian keyword search; **no semantic matching** (see Limits) |
|
|
128
|
+
| Citations | library id | ZIM name, edition date, entry path, anchor |
|
|
129
|
+
|
|
130
|
+
Pick Context7 when you need the newest release of a fast-moving library and are online. Pick zimlore when you
|
|
131
|
+
are offline, behind a policy that forbids sending queries out, want a reproducible snapshot, or want the same
|
|
132
|
+
documents every run. They are not exclusive.
|
|
133
|
+
|
|
134
|
+
## What Kiwix has for developers
|
|
135
|
+
|
|
136
|
+
Measured against the live catalogue on 2026-11-07 (`docs/research.md` has the method):
|
|
137
|
+
|
|
138
|
+
- **243 DevDocs ZIMs**, 0.67 GB in total (median 0.85 MB, largest scikit-learn at 58 MB), dated 2026-05-04 to
|
|
139
|
+
2026-10-31: Python, Rust, Go, Node, React, Vue, TypeScript, Django, Flask, FastAPI, PostgreSQL, Docker,
|
|
140
|
+
Kubernetes, Terraform and more. This is the Context7-shaped part, and it is tiny.
|
|
141
|
+
- `docs.python.org_en_all` (3.96 GB, 2026-08-28), `peps.python_en_all`, `devhints.io_en_all`, `archlinux_en_all`,
|
|
142
|
+
`gentoo_en_all`, `www.mankier.com_en_all` (man pages), `php.net_en_all` (**2024-08-13**, old).
|
|
143
|
+
- **Stack Exchange**: 181 English-filter ZIMs, 191 GB in total; `stackoverflow.com_en_all` alone is 114.9 GB
|
|
144
|
+
(2026-07-02).
|
|
145
|
+
|
|
146
|
+
## Limits
|
|
147
|
+
|
|
148
|
+
- **Keyword search, not semantic.** On 34 questions paraphrased from page titles (`scripts/eval_retrieval.py`,
|
|
149
|
+
DevDocs Flask and Python), the right page was first for **10/34**, in the top 5 for **23/34**, MRR 0.436.
|
|
150
|
+
Questions that share words with the page do well; "isolated environment for project packages" does not find
|
|
151
|
+
"Virtual Environments and Packages". Searching with the library's own vocabulary works much better. With no
|
|
152
|
+
library given, 1 of 4 questions found its page: results from different ZIMs are interleaved because libzim
|
|
153
|
+
exposes no comparable score.
|
|
154
|
+
- **No upstream version.** The ZIM date says when the docs were scraped; which release they document is on the
|
|
155
|
+
pages.
|
|
156
|
+
- **Licences are not in the ZIMs we measured.** Neither DevDocs ZIM had a `License` metadata key; `zim_cite`
|
|
157
|
+
says "not stated in the ZIM" rather than guessing. DevDocs itself is MPL-2.0, but the text it archives is
|
|
158
|
+
each upstream project's.
|
|
159
|
+
- **`libzim` is GPL-3.0-or-later** (the binding; the C++ library is GPL-2.0 per its repository). zimlore is MIT
|
|
160
|
+
and imports it at run time; see `PLAN.md` for what that means for redistribution.
|
|
161
|
+
- Wheels exist for Windows x64, macOS and Linux (glibc and musl) on CPython 3.11+. On Windows ARM64 `pip
|
|
162
|
+
install zimlore` succeeds without `libzim`, and every tool reports the missing binding and the remedy.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
python -m pip install -e . --group dev # pip 25.1+
|
|
168
|
+
python -m pytest -q # no ZIM needed: tests build a synthetic one with libzim's writer
|
|
169
|
+
python -m ruff check . ; python -m ruff format --check . ; python -m mypy zimlore tests
|
|
170
|
+
python scripts/eval_retrieval.py # needs devdocs_en_flask and devdocs_en_python installed
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
[ROADMAP.md](ROADMAP.md) lists every obligation with the test that would fail if it regressed;
|
|
174
|
+
[PLAN.md](PLAN.md) records the design decisions; [docs/research.md](docs/research.md) the sources.
|