kobo-hardcover-sync 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.
Files changed (87) hide show
  1. kobo_hardcover_sync-0.1.0/.gitignore +8 -0
  2. kobo_hardcover_sync-0.1.0/CHANGELOG.md +41 -0
  3. kobo_hardcover_sync-0.1.0/LICENSE +21 -0
  4. kobo_hardcover_sync-0.1.0/PKG-INFO +435 -0
  5. kobo_hardcover_sync-0.1.0/README.md +404 -0
  6. kobo_hardcover_sync-0.1.0/THIRD-PARTY-NOTICES.md +91 -0
  7. kobo_hardcover_sync-0.1.0/docs/hardcover-api.md +81 -0
  8. kobo_hardcover_sync-0.1.0/docs/how-it-works.md +199 -0
  9. kobo_hardcover_sync-0.1.0/docs/img/books.png +0 -0
  10. kobo_hardcover_sync-0.1.0/docs/img/match.png +0 -0
  11. kobo_hardcover_sync-0.1.0/docs/img/phone-night.png +0 -0
  12. kobo_hardcover_sync-0.1.0/docs/local-mode.md +220 -0
  13. kobo_hardcover_sync-0.1.0/docs/releasing.md +49 -0
  14. kobo_hardcover_sync-0.1.0/docs/sync-rules.md +319 -0
  15. kobo_hardcover_sync-0.1.0/examples/docker-compose.example.yml +49 -0
  16. kobo_hardcover_sync-0.1.0/packaging/homebrew/README.md +84 -0
  17. kobo_hardcover_sync-0.1.0/pyproject.toml +88 -0
  18. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/__init__.py +17 -0
  19. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/__main__.py +3 -0
  20. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/cli.py +317 -0
  21. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/__init__.py +10 -0
  22. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/config.py +79 -0
  23. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/linux.py +205 -0
  24. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/macos.py +250 -0
  25. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/page.py +213 -0
  26. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/platform.py +84 -0
  27. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/remote.py +70 -0
  28. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/computer/runner.py +270 -0
  29. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/__init__.py +0 -0
  30. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/collection.py +559 -0
  31. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/hardcover.py +531 -0
  32. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/job.py +170 -0
  33. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/kobo_db.py +180 -0
  34. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/plan.py +109 -0
  35. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/state.py +257 -0
  36. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/engine/syncback.py +145 -0
  37. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/env.py +9 -0
  38. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/server/__init__.py +0 -0
  39. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/server/accounts.py +427 -0
  40. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/server/proxy.py +50 -0
  41. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/server/stats.py +53 -0
  42. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/server/upload.py +109 -0
  43. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/__init__.py +0 -0
  44. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/account_pages.py +280 -0
  45. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/app.py +1231 -0
  46. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/covers.py +60 -0
  47. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/fmt.py +49 -0
  48. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/apple-touch-icon.png +0 -0
  49. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/favicon.svg +10 -0
  50. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Literata-LICENSE.txt +93 -0
  51. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Literata-latin-ext.woff2 +0 -0
  52. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Literata-latin.woff2 +0 -0
  53. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/README.txt +16 -0
  54. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Sora-LICENSE.txt +93 -0
  55. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Sora-latin-ext.woff2 +0 -0
  56. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/fonts/Sora-latin.woff2 +0 -0
  57. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/img/CREDITS.txt +12 -0
  58. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/img/kobo-day.webp +0 -0
  59. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/img/kobo-night.webp +0 -0
  60. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/kobo.css +484 -0
  61. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/static/kobo.js +148 -0
  62. kobo_hardcover_sync-0.1.0/src/kobo_hardcover_sync/web/strings.py +375 -0
  63. kobo_hardcover_sync-0.1.0/tests/__init__.py +17 -0
  64. kobo_hardcover_sync-0.1.0/tests/conftest.py +31 -0
  65. kobo_hardcover_sync-0.1.0/tests/kobo_fixture.py +109 -0
  66. kobo_hardcover_sync-0.1.0/tests/test_accounts.py +345 -0
  67. kobo_hardcover_sync-0.1.0/tests/test_collection.py +466 -0
  68. kobo_hardcover_sync-0.1.0/tests/test_computer.py +505 -0
  69. kobo_hardcover_sync-0.1.0/tests/test_core.py +158 -0
  70. kobo_hardcover_sync-0.1.0/tests/test_covers.py +76 -0
  71. kobo_hardcover_sync-0.1.0/tests/test_design_tokens.py +56 -0
  72. kobo_hardcover_sync-0.1.0/tests/test_env.py +10 -0
  73. kobo_hardcover_sync-0.1.0/tests/test_hardcover.py +400 -0
  74. kobo_hardcover_sync-0.1.0/tests/test_hardcover_client.py +267 -0
  75. kobo_hardcover_sync-0.1.0/tests/test_homebrew.py +87 -0
  76. kobo_hardcover_sync-0.1.0/tests/test_licences.py +71 -0
  77. kobo_hardcover_sync-0.1.0/tests/test_linux.py +237 -0
  78. kobo_hardcover_sync-0.1.0/tests/test_local.py +535 -0
  79. kobo_hardcover_sync-0.1.0/tests/test_package.py +86 -0
  80. kobo_hardcover_sync-0.1.0/tests/test_proxy.py +114 -0
  81. kobo_hardcover_sync-0.1.0/tests/test_publishable.py +95 -0
  82. kobo_hardcover_sync-0.1.0/tests/test_readme.py +70 -0
  83. kobo_hardcover_sync-0.1.0/tests/test_rules.py +360 -0
  84. kobo_hardcover_sync-0.1.0/tests/test_rules_document.py +40 -0
  85. kobo_hardcover_sync-0.1.0/tests/test_stats.py +39 -0
  86. kobo_hardcover_sync-0.1.0/tests/test_upload.py +114 -0
  87. kobo_hardcover_sync-0.1.0/tests/test_web.py +307 -0
@@ -0,0 +1,8 @@
1
+ data/
2
+ config/
3
+ __pycache__/
4
+ .pytest_cache/
5
+ .venv/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ What changed, for people who use the tool. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Before 1.0, a
6
+ minor version may change behaviour; the notes will say so.
7
+
8
+ ## 0.1.0 - 2026-10-02
9
+
10
+ The first public release.
11
+
12
+ ### What it does
13
+
14
+ - Reads a stock Kobo's database when the Kobo is plugged in, and sends
15
+ status, progress and dates to Hardcover for the books switched on.
16
+ - Two ways to run it: on your own computer (local mode), or on your own
17
+ server for a household behind a sign-in proxy (server mode).
18
+ - A dry run until you go live. A match is never guessed.
19
+ - Takes over edits made on Hardcover: a status, a finish date, an edition,
20
+ a book taken off the shelf.
21
+ - An optional collection on the Kobo with the books that sync, written
22
+ only behind a version gate and after a backup.
23
+ - A page for choosing books, fixing matches and settings; light and dark;
24
+ works on a phone and without JavaScript.
25
+ - macOS and desktop Linux: a trigger on plug-in, the secret store for
26
+ tokens, notifications.
27
+
28
+ ### Tested
29
+
30
+ - With a real device: one Kobo Clara Colour (software 6.0.274403), on
31
+ macOS 27, in server mode.
32
+ - Local mode on macOS 27, with that Kobo: installed on a clean account
33
+ from the README, synced, uninstalled.
34
+ - The Homebrew formula: built from source and tested on macOS 27; an
35
+ upgrade kept the Full Disk Access permission.
36
+ - A Hardcover token with only the four permissions the tool asks for.
37
+ - Everything on Linux: tested without a device.
38
+
39
+ ### Known limits
40
+
41
+ See the end of [docs/sync-rules.md](docs/sync-rules.md).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Merlijn Tishauser
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,435 @@
1
+ Metadata-Version: 2.5
2
+ Name: kobo-hardcover-sync
3
+ Version: 0.1.0
4
+ Summary: Reading progress from a stock Kobo e-reader to Hardcover, on your own computer or your own server
5
+ Project-URL: Homepage, https://github.com/merlijntishauser/kobo-hardcover-sync
6
+ Project-URL: Documentation, https://github.com/merlijntishauser/kobo-hardcover-sync#readme
7
+ Project-URL: Changelog, https://github.com/merlijntishauser/kobo-hardcover-sync/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/merlijntishauser/kobo-hardcover-sync/issues
9
+ Author-email: Merlijn Tishauser <merlijn@ccadd.nl>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ License-File: THIRD-PARTY-NOTICES.md
13
+ Keywords: e-reader,hardcover,kobo,reading,sync
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Environment :: Web Environment
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: cryptography==50.0.2
26
+ Requires-Dist: fastapi==0.115.6
27
+ Requires-Dist: python-multipart==0.0.20
28
+ Requires-Dist: pyyaml==6.0.2
29
+ Requires-Dist: uvicorn==0.34.0
30
+ Description-Content-Type: text/markdown
31
+
32
+ # kobo-hardcover-sync
33
+
34
+ <img src="https://raw.githubusercontent.com/merlijntishauser/kobo-hardcover-sync/v0.1.0/src/kobo_hardcover_sync/web/static/favicon.svg" width="48" alt="" align="right">
35
+
36
+ Keeps your shelf on [Hardcover](https://hardcover.app) in step with what
37
+ you read on a **Kobo e-reader**: which books, how far, finished when. It
38
+ works with the Kobo as it comes, with books bought in the **Kobo store**.
39
+
40
+ Plug the Kobo into your computer and it syncs. Nothing is installed on the
41
+ Kobo, and nothing about how you read changes.
42
+
43
+ ![The Books page: a list of books, each with a reading line, a Sync switch and what Hardcover will be told](https://raw.githubusercontent.com/merlijntishauser/kobo-hardcover-sync/v0.1.0/docs/img/books.png)
44
+
45
+ > **Beta.** It has been used daily on one Kobo and one Mac. It can write
46
+ > one thing to your Kobo, a collection, and only if you ask for it; see
47
+ > [what is written to the Kobo](#the-collection-on-the-kobo). Read that
48
+ > part before you give the collection a name.
49
+
50
+ An independent project: not affiliated with, endorsed by or sponsored by
51
+ Rakuten Kobo or Hardcover.
52
+
53
+ ## What it does
54
+
55
+ - Reads the Kobo's own database when the Kobo is plugged in. That is the
56
+ only place a stock Kobo keeps your reading; Kobo's cloud has no public
57
+ API.
58
+ - Sends **status, progress and dates** to Hardcover for the books you
59
+ switched on. A book you finished becomes *Read* with its date; a book
60
+ you are reading becomes *Currently reading* with how far you are.
61
+ - Starts as a **dry run**: it shows what it would send, and sends nothing
62
+ until you say so.
63
+ - **Never guesses a match.** A book it cannot place on Hardcover by ISBN,
64
+ or by title and author together, waits for you to choose.
65
+ - Takes over what you change on Hardcover (a status, a finish date, an
66
+ edition) instead of overwriting it.
67
+ - Can put a **collection** of your synced books on the Kobo. Handy when a
68
+ Kobo account is shared and the device lists everyone's books.
69
+ - Keeps your reading minutes, for a dashboard of your own if you have one.
70
+
71
+ It does not sync highlights, notes or ratings, and it does not manage
72
+ books: it works with whatever is on the Kobo.
73
+
74
+ Every rule it follows is written down, each with the test that holds it:
75
+ [docs/sync-rules.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/sync-rules.md).
76
+
77
+ ## Two ways to run it
78
+
79
+ - **On your own computer** (local mode). One reader, no server. A sync
80
+ runs when you plug in the Kobo; a page in your browser shows your books
81
+ when you ask for it.
82
+ - **On your own server, for a household** (server mode). Several readers,
83
+ each with their own books and their own Hardcover account, behind the
84
+ sign-in you already have. The computer the Kobo is plugged into sends
85
+ the book list to the server.
86
+
87
+ Either way it is yours: there is no account with this project and no
88
+ service of its own.
89
+
90
+ ## Is this the right tool for you?
91
+
92
+ There are other good ways to get reading progress onto Hardcover. As far
93
+ as their own documentation said on 2 October 2026:
94
+
95
+ | | What it needs | Choose it when |
96
+ |---|---|---|
97
+ | **kobo-hardcover-sync** | A computer to plug the Kobo into. Nothing on the Kobo. | You read Kobo-store books on an unmodified Kobo, or your Kobo's software is too new for the tools below. |
98
+ | [NickelHardcover](https://codeberg.org/StrayRose/NickelHardcover) | An add-on installed on the Kobo itself. Its README says firmware 5.x is not supported yet. | Your Kobo's firmware is supported and you are happy to modify the device. It syncs over Wi-Fi without a computer, and also does highlights, notes and reviews. |
99
+ | [KOReader with the Hardcover plugin](https://github.com/Billiam/hardcoverapp.koplugin) | KOReader on the device. KOReader cannot open the store's DRM books. | You read your own, DRM-free books in KOReader. |
100
+ | [Calibre-Web Automated](https://github.com/crocodilestick/Calibre-Web-Automated) | A Calibre library on a server that the Kobo syncs with. | Your books live in Calibre and reach the Kobo from there. |
101
+
102
+ ## What you need
103
+
104
+ - A Kobo e-reader and its USB cable.
105
+ - A Mac, or a Linux computer with a desktop (GNOME, KDE and the like).
106
+ - A [Hardcover](https://hardcover.app) account.
107
+ - [Homebrew](https://brew.sh) on a Mac, or [uv](https://docs.astral.sh/uv/)
108
+ on any computer, to install the tool.
109
+
110
+ What has been seen working with a real Kobo:
111
+
112
+ | | Tested |
113
+ |---|---|
114
+ | Kobo | Clara Colour, Kobo software 6.0.274403 |
115
+ | macOS 27, with a server | daily use since 1 October 2026 |
116
+ | macOS 27, local mode | a clean account, from this README: install, sync, uninstall (2 October 2026) |
117
+ | Desktop Linux | built and tested without a device; not yet with a real Kobo |
118
+
119
+ Syncing to Hardcover reads the Kobo and should work on other models and
120
+ software. Writing the collection is held back on any Kobo database version
121
+ that has not been tried: see below. If it works on yours, please say so in
122
+ an issue, with the model and the software version.
123
+
124
+ ## Install and first run, on your own computer
125
+
126
+ On a Mac, with Homebrew:
127
+
128
+ ```
129
+ brew install merlijntishauser/tap/kobo-hardcover-sync
130
+ ```
131
+
132
+ It is built on your Mac from source, which takes a few minutes the first
133
+ time. On any computer, with uv, which brings the Python it needs:
134
+
135
+ ```
136
+ uv tool install kobo-hardcover-sync
137
+ ```
138
+
139
+ Then, either way:
140
+
141
+ ```
142
+ kobo-hardcover-sync setup
143
+ kobo-hardcover-sync token
144
+ kobo-hardcover-sync open
145
+ ```
146
+
147
+ 1. **`setup`** makes the trigger that runs a sync when the Kobo is plugged
148
+ in, and says what it did.
149
+
150
+ On a Mac it builds a small app, `KoboHardcoverSync.app`, and that app
151
+ needs one permission that macOS does not ask for by itself: **Full Disk
152
+ Access**. System Settings, Privacy & Security, Full Disk Access, the
153
+ plus button, then Cmd+Shift+G and the path `setup` printed. Without it
154
+ the sync cannot read the Kobo.
155
+
156
+ On Linux no permission is needed.
157
+
158
+ 2. **`token`** asks for your Hardcover token, checks it with Hardcover and
159
+ stores it in your computer's secret store (the Keychain on a Mac). It
160
+ prints a link to Hardcover's page for making a token, with the four
161
+ permissions this tool needs already ticked: your profile name, the
162
+ catalogue, reading your library, changing your library. A token stops
163
+ working on the date you choose there; you then make a new one.
164
+
165
+ 3. **Plug in the Kobo** and tap *Connect* on it. The first sync reads your
166
+ books.
167
+
168
+ 4. **`open`** shows them in your browser. Everything you read before today
169
+ is listed and switched *Off*: choose the books that are yours and switch
170
+ them *On*. Books you open on the Kobo from now on switch on by
171
+ themselves.
172
+
173
+ 5. Look at what it would send. Fix the books that say *Needs a Hardcover
174
+ match*. Then, under Settings, go **live**.
175
+
176
+ ![Choosing the Hardcover book for a title it was not sure about](https://raw.githubusercontent.com/merlijntishauser/kobo-hardcover-sync/v0.1.0/docs/img/match.png)
177
+
178
+ ## Every day
179
+
180
+ Plug in the Kobo. A notification says what happened, for example:
181
+
182
+ > Kobo synced. 8 book(s) updated.
183
+
184
+ That is all. Nothing runs in between: the sync starts when the Kobo is
185
+ mounted and exits when it is done. Open the page when you want to switch a
186
+ book on or off, fix a match, set a finish date, or see why something did
187
+ not sync.
188
+
189
+ Other commands:
190
+
191
+ | | |
192
+ |---|---|
193
+ | `kobo-hardcover-sync sync` | One sync now, from a terminal. Without a Kobo it still exchanges changes with Hardcover (local mode). |
194
+ | `kobo-hardcover-sync status` | What is set up, the Kobo it sees, its model and software, the last message. |
195
+ | `kobo-hardcover-sync open` | The page. |
196
+ | `kobo-hardcover-sync token --remove` | Forget the Hardcover token and go back to dry run. |
197
+ | `kobo-hardcover-sync uninstall` | Remove the trigger. `--purge` also removes the state and the tokens. |
198
+
199
+ <img src="https://raw.githubusercontent.com/merlijntishauser/kobo-hardcover-sync/v0.1.0/docs/img/phone-night.png" alt="The same page on a phone, at night" width="300">
200
+
201
+ ## The collection on the Kobo
202
+
203
+ Optional. Give the collection a name under Settings and the Kobo gets a
204
+ collection holding the books that sync. This is **the only thing this tool
205
+ ever writes to your Kobo**. Without a name, it writes nothing at all.
206
+
207
+ What is written, and how:
208
+
209
+ - Only a collection this tool made itself. A collection of yours is never
210
+ touched, also when it has the same name.
211
+ - Only when the list changed.
212
+ - In one step that happens completely or not at all.
213
+ - After a copy of the Kobo's whole database was saved on your computer.
214
+ The last three copies are kept.
215
+ - The collection is the tool's: a book you add to it on the Kobo is taken
216
+ out again. Another name under Settings replaces the old collection at
217
+ the next plug-in; clearing the name removes it.
218
+
219
+ **A gate comes before all of that.** The Kobo's database says which version
220
+ it is. A version nobody has tried is refused: nothing is written, not even
221
+ the copy, and the message names the version. Syncing to Hardcover goes on
222
+ as usual.
223
+
224
+ | Kobo software | Database version | Seen working |
225
+ |---|---|---|
226
+ | 6.0.274403 | 222 | 1 October 2026, one device |
227
+
228
+ To try another version on purpose, put `allow_untested_kobo = true` in
229
+ `config.toml` in the tool's folder. Even then it refuses a database that
230
+ lacks a column it writes, or that has one it does not know how to fill in.
231
+ If it works, please report the versions.
232
+
233
+ **Always eject before unplugging** after a notification says the collection
234
+ changed.
235
+
236
+ ### If something looks wrong on the Kobo
237
+
238
+ 1. The gentle way: delete the collection on the Kobo itself (*My Books*,
239
+ *Collections*). Your books and your reading are not part of it. Clear
240
+ the name under Settings if you do not want it back.
241
+ 2. The whole database back, as it was before the write. Plug in the Kobo,
242
+ then on a Mac:
243
+
244
+ ```
245
+ cd ~/Library/Application\ Support/kobo-hardcover-sync/collection/backups
246
+ gunzip -c KoboReader-<date>.sqlite.gz > /Volumes/KOBOeReader/.kobo/KoboReader.sqlite
247
+ ```
248
+
249
+ Eject, then unplug. Everything on the device goes back to that moment,
250
+ reading positions included; for Kobo-store books the Kobo fetches the
251
+ newer positions from your account at its next sync.
252
+
253
+ ## For a household, on your own server
254
+
255
+ The server holds the books and the settings of every reader, and talks to
256
+ Hardcover for each of them. It has **no login of its own**: it sits behind
257
+ a sign-in proxy you already run (forward authentication) and believes the
258
+ `Remote-User` header only from that proxy's address.
259
+
260
+ ```
261
+ docker build -t kobo-hardcover-sync .
262
+ cp examples/docker-compose.example.yml docker-compose.yml
263
+ python3 -c "import base64,os;print('KHS_SECRET_KEY='+base64.urlsafe_b64encode(os.urandom(32)).decode())" > .env
264
+ chmod 600 .env && mkdir -p data
265
+ docker compose up -d
266
+ ```
267
+
268
+ - Set `KHS_TRUSTED_PROXIES` in the compose file to the address your proxy
269
+ connects from. The server refuses to start without it, and answers 403
270
+ to anyone else who sends a `Remote-User` header. Publish the port only
271
+ where the proxy can reach it.
272
+ - Open the page through the proxy and press *Start using Kobo Hardcover
273
+ Sync*. The first reader is the admin. Everyone else the proxy lets in
274
+ signs up with one click and starts in dry run.
275
+ - Each reader enters their own Hardcover token under Settings. Tokens are
276
+ encrypted in the database with `KHS_SECRET_KEY`; keep that key out of the
277
+ data folder and its backups.
278
+ - `/upload` and `/collection` are for the computers that send a Kobo's
279
+ books: route them past the sign-in, because they carry a token of their
280
+ own. `/api/stats/<reader>` gives reading minutes and the current book to
281
+ a dashboard, once that reader made a stats token under Settings.
282
+
283
+ On the computer the Kobo is plugged into:
284
+
285
+ ```
286
+ kobo-hardcover-sync setup --server https://kobo.example.org
287
+ ```
288
+
289
+ `setup` makes an upload token, keeps it in the secret store and prints its
290
+ SHA-256. Paste that hash on the page under Settings, Devices. The hash
291
+ alone cannot upload anything.
292
+
293
+ Settings of the server, as environment variables: `KHS_DATA`, `KHS_HOST`,
294
+ `KHS_TRUSTED_PROXIES`, `KHS_SECRET_KEY`, `KHS_INTERVAL` (seconds between
295
+ syncs with Hardcover, 0 for never), `KHS_MAX_UPLOAD_MB`,
296
+ `KHS_SNAPSHOT_DAYS`, `KHS_COVER_URL`, and `TZ` for the time zone.
297
+
298
+ ## Privacy
299
+
300
+ There is no telemetry, no update check and no account with this project.
301
+ This is everything that leaves the computer it runs on.
302
+
303
+ **To Hardcover**, with your token:
304
+
305
+ - to find a book: its ISBN, or its title and author. Only for books that
306
+ are switched on, and for a search you type yourself;
307
+ - for those books: the status, how far you are, and the dates.
308
+
309
+ **To Kobo's cover server**: the cover number of each book the page shows.
310
+ The tool fetches the cover once and keeps it; your browser talks to the
311
+ tool only.
312
+
313
+ **In server mode, from the computer to your own server**: for every book
314
+ on the Kobo, the title, author, ISBN, progress, dates, cover number and
315
+ language, and the times books were opened on that Kobo. On a shared Kobo
316
+ account that includes the books of the others on the account; each reader
317
+ decides which are theirs. The server keeps what was uploaded for 90 days.
318
+
319
+ **Never, in either mode**: the Kobo account's login tokens, the keys of
320
+ your books, reviews, wishlist or anything else in the Kobo's database. The
321
+ upload is built from a list of what is needed, and the server refuses an
322
+ upload that holds more.
323
+
324
+ Your Hardcover token is kept in your computer's secret store (local mode)
325
+ or encrypted in the server's database (server mode). It is never shown
326
+ again, never logged, and never put on a command line.
327
+
328
+ ## Limits worth knowing
329
+
330
+ - The Kobo has to be plugged into a computer. There is no sync over Wi-Fi.
331
+ - Edits you make on Hardcover are seen at the next sync, not at once.
332
+ - Finish dates are the Kobo's own, which are in UTC: a book finished just
333
+ after midnight can carry the day before. Set the date on the page when
334
+ it matters.
335
+ - Two copies of one book on the Kobo (a sample and the book, two editions):
336
+ the copy you read last speaks for the book on Hardcover.
337
+ - Books you put on the Kobo yourself can sync to Hardcover, but are not put
338
+ in the collection.
339
+ - A very large first sync can take more than a day: Hardcover allows 5000
340
+ requests a day. It carries on at the next sync.
341
+
342
+ The full list is at the end of [docs/sync-rules.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/sync-rules.md).
343
+
344
+ ## When something does not work
345
+
346
+ Start with `kobo-hardcover-sync status`. The log is `agent.log` in the
347
+ tool's folder: `~/Library/Application Support/kobo-hardcover-sync/` on a
348
+ Mac, `~/.local/share/kobo-hardcover-sync/` on Linux.
349
+
350
+ | You see | What to do |
351
+ |---|---|
352
+ | Nothing happens when the Kobo is plugged in | Tap *Connect* on the Kobo. On a Mac, check that `KoboHardcoverSync.app` has Full Disk Access. Run `kobo-hardcover-sync sync` in a terminal to see the message. |
353
+ | "Could not read the Kobo's database. Does the app have Full Disk Access?" | Give the app the permission (step 1 above). If you rebuilt the app, remove its old entry there first and add it again. |
354
+ | "Nothing new." | The Kobo is as it was at the last sync. Read a page and plug it in again. |
355
+ | "Hardcover does not accept your token" | It expired or was removed. Make a new one: `kobo-hardcover-sync token`, or Settings on a server. |
356
+ | "Your Hardcover token may not do this (it lacks: ...)" | The token was made without one of the four permissions. Make a new one with the link the tool gives. |
357
+ | *Needs a Hardcover match* on a book | Open its Details and choose the right book, or search for it there. |
358
+ | "Collection not updated: ..." with a database version | Your Kobo's software has not been tried yet. Syncing to Hardcover still works. See [the collection](#the-collection-on-the-kobo). |
359
+ | "None of the N books this tool put on your Hardcover shelf are on the shelf ..." | The token belongs to another Hardcover account, or you emptied the shelf yourself. Nothing was switched off. Fix the token, or switch those books off on the page. |
360
+ | "Hardcover could not be reached" or "Today's number of requests ... is used up" | Nothing is lost. The next sync carries on. |
361
+ | The server answers 403 to everything, or does not start | `KHS_TRUSTED_PROXIES` does not name the address your proxy connects from. |
362
+
363
+ On Linux, a Kobo mounted somewhere unusual is found when `KHS_VOLUMES`
364
+ names the folder above it. Without a desktop keyring the tokens are kept in
365
+ a file only you can read; `status` says which.
366
+
367
+ ## Uninstall
368
+
369
+ ```
370
+ kobo-hardcover-sync uninstall --purge
371
+ brew uninstall kobo-hardcover-sync # or: uv tool uninstall kobo-hardcover-sync
372
+ ```
373
+
374
+ Run the first line first: it removes the trigger, and with `--purge` your
375
+ books, settings and tokens on this computer. Without `--purge` they stay.
376
+
377
+ On a Mac, remove the app's entry under Full Disk Access by hand. A
378
+ collection it made stays on the Kobo until you delete it there; clearing
379
+ its name under Settings before you uninstall removes it at the next
380
+ plug-in.
381
+
382
+ ## More to read
383
+
384
+ - [docs/sync-rules.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/sync-rules.md): every rule, and the test that
385
+ holds it.
386
+ - [docs/how-it-works.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/how-it-works.md): the parts, and why it is
387
+ built this way.
388
+ - [docs/local-mode.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/local-mode.md): local mode in detail.
389
+ - [docs/hardcover-api.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/docs/hardcover-api.md): what Hardcover's API asks
390
+ of a tool like this.
391
+ - [CONTRIBUTING.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/CONTRIBUTING.md), [SECURITY.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/SECURITY.md),
392
+ [CHANGELOG.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/CHANGELOG.md).
393
+
394
+ ## What 1.0 would need
395
+
396
+ This is 0.1: it works for the one household it was built in. Before it is
397
+ called 1.0:
398
+
399
+ - more than one Kobo model and more than one software version confirmed,
400
+ for syncing and for the collection;
401
+ - local mode on a Mac and the Linux desktop seen working with real Kobos,
402
+ by more people than its author;
403
+ - a season of use without a damaged Kobo database, on more than one
404
+ device;
405
+ - Hardcover's API out of beta, or its changes handled as they came;
406
+ - signing in to Hardcover without pasting a token.
407
+
408
+ ## AI disclaimer
409
+
410
+ Yes, LLMs and coding agents are (and will be) used in this project. We
411
+ integrate AI carefully and responsibly, and never in a way that compromises
412
+ data integrity, privacy, or security.
413
+
414
+ Please be responsible and transparent when using AI tools, and always put
415
+ user privacy and data security first.
416
+
417
+ When you submit a PR, make sure you fully understand what the code does and
418
+ what it might affect. Keep PRs small, and always run the tests before
419
+ opening or updating one.
420
+
421
+ And a small personal note: using AI tools doesn't mean this project didn't
422
+ take a lot of time and effort. Built with care, and with real appreciation
423
+ for [Hardcover](https://hardcover.app) and the people who build it: a place
424
+ for readers that is open enough to let a tool like this exist. And Kobo:
425
+ great e-readers, but finally give us a family account!
426
+
427
+ ## Licence
428
+
429
+ The code, the documentation and the icon are under the [MIT licence](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/LICENSE).
430
+
431
+ Two kinds of files in this repository are not, and keep their own licence:
432
+ the two fonts (SIL Open Font License 1.1) and the two photographs (Unsplash
433
+ License). What that means, where their licence texts are, and the licences
434
+ of the Python packages installed next to the tool:
435
+ [THIRD-PARTY-NOTICES.md](https://github.com/merlijntishauser/kobo-hardcover-sync/blob/v0.1.0/THIRD-PARTY-NOTICES.md).