# Library Hub: agent docs, in one file > Built 2026-10-30. The files in the order llms.txt lists them. # AGENTS.md Source: https://insurgently.com/projects/library-hub/technical/agents/AGENTS.md You're in a plugin that calls Library Hub through `@insurgently/library-hub-api`. The contract is `dist/index.d.ts` in this package. Follow these rules: 1. **Use the getter.** Reach Library Hub with `getLibraryHub(app, wanted, { by })` or `onLibraryHubReady(app, plugin, wanted, cb)`. Never `app.plugins.getPlugin('library-hub')` yourself. 2. **Gate on the lowest minor you need.** `wanted` is a version like `'0.8.0'`. Don't raise it just because the types are newer. 3. **Feature-detect anything newer than the gate.** A method tagged `@since` a later minor than your gate may be missing: `if (hasMethod(api, 'bookText'))`. 4. **Re-register on the ready event.** Library Hub can be turned off and on. `onLibraryHubReady` calls you again with the new API object; drop the old one. 5. **Treat the error `code` as the contract.** Branch on `e.code` (`server-off`, `book-missing`, `needs-conversion`…), never on the message. Show the message to the user as it is. 6. **Never import plugin source.** Only this package. Its runtime part is a few helpers with no dependencies. Also: a `BookRef` can be a number, a string or `{ isbn: … }`; pass what you have. `bookText` only converts an AZW3 or MOBI with `{ convert: true }`, and the user is asked first. A chapter's `location` goes to `openBook` unchanged. # Changelog Source: https://insurgently.com/projects/library-hub/technical/agents/CHANGELOG.md Changes to Library Hub's API, by version. Within 0.x the API only adds and deprecates, and removes at 1.0; a change in behavior is marked. ## 0.9.0 Served by Library Hub 0.1.0 and later; the first version published as a package (0.8.0 was never published). Everything new is an addition, except the changes marked below. - `listBooks('all')`: every book in Calibre, by id. - `BookSummary.context`: what the book is about, as plain text, from the note's `context` field, else Calibre's comments. - `BookChapters.outline` (and so `BookText.outline`): whether the chapters follow the book's own structure, an EPUB's files or a PDF's outline. False for a PDF cut into even parts. Before, only the warning text said so. - **Changed:** `bookText` and `bookChapters` give every chapter of an EPUB: past 30 they're no longer merged into their parts (the chapter notes still merge them, and `openBook`'s `chapter` number counts the notes' chapters, so open a chapter at its `location`). A file several contents entries point into is split at their anchors, each such chapter located `file#anchor`; a file no entry claims, with more than a page heading's text, is a chapter of its own; and contents entries are found when the NCX sits in another folder than the OPF. These chapter rules don't change the whole text (reading it with foliate-js, below, does, once). - **Changed:** `bookText` and `bookChapters` read EPUB, AZW3 and MOBI files with foliate-js, the parser Library Hub's reader already shows them with, instead of Library Hub's own EPUB reading. An EPUB's contents come from its nav document or its NCX, as foliate-js gives them; HTML entities in a contents file no longer lose the contents. The whole text keeps its paragraphs and blocks as blank lines, and a line break (`
`) is now a single line break; spaces next to a paragraph break are gone and every HTML entity is decoded. So **every EPUB's `hash` changes once**, and offsets may move a little; a corpus re-reads the passages whose words changed. - **Changed:** a book with only an AZW3 or MOBI has its text read from that file as it is, with no conversion, with or without the Content Server: `format` is `AZW3` or `MOBI` (`BookChapters.format` takes those two), and each chapter's `location` is the reader's own (`kindle:pos:…`, `filepos:…`, or a section's `epubcfi(…)`), which `openBook` opens. `needs-conversion` and `{ convert: true }` now only matter for an AZW3 or MOBI that can't be read (DRM, a damaged file), and the message names the cause. `openBook` opens a book's AZW3 or MOBI when it has no EPUB, before its PDF. `bookFile(book, 'EPUB', { convert: true })` still asks Calibre to make an EPUB. - **Changed:** with Calibre's Content Server off and a library folder set, `bookFile`, `pdfText`, `bookText` and `bookChapters` read a Calibre book's files straight from the library folder, read-only, instead of rejecting with `server-off`. Converting an AZW3 or MOBI still needs the server: `bookFile` with `{ convert: true }` rejects with `server-off`, and `bookText`/`bookChapters` reject with `needs-conversion` when the book has a PDF (with `convert`, the PDF is read, with a warning) and with `server-off` when it has none. A file in a cloud-synced folder that isn't downloaded on this computer, or takes too long to download, rejects with `server-error` naming the file; a library file caught while Calibre is writing it is read again a few times, then rejects with `busy`. `status().calibre` in file mode now has `files: true` and says files and text are read from the library folder. - **Changed:** `openBook` at a `quote`, and a highlight finding its text again, match through `@insurgently/text`'s `fold`: decomposed and precomposed accents, curly and straight quotes and apostrophes (with ʼ, ʻ, backticks and guillemets), soft hyphens, zero-width characters, Greek final sigma, Turkish İ, German ß, and hyphenated, closed or line-split words (`well-known`, `well-\nknown`, `wellknown`, OCR's `¬`) now match each other; a quote never matches half a letter. Whitespace and quote marks are still ignored altogether. `summarizeBook`'s chapter notes keep a quote found word for word by the same rules, so a quote a model typed with straight quotes is no longer dropped. - **Changed:** with Model Hub 0.13 or later, `estimateSummary` and `estimateShelfFromDescription` are priced by Model Hub's `estimate()`, each call as it would be sent with its reply cap: `usd` is the high end of its range (every call with a full reply), and `text` gives the range, "about $0.02–$0.09". When Model Hub can't price a call, `usd` is null and `text` says why. With Model Hub 0.12, both work as before: the input at the role's first model's listed price. ## 0.8.0 Never published as a package on its own: it ships inside 0.9.0. - `bookText(book, { convert? })` and `bookChapters(book, { convert? })`: a book's text as chapters. Each chapter has `n`, `title`, a `location` that `openBook` lands on (`OEBPS/c3.xhtml` in an EPUB, `chars a-b` in a PDF), `start` and `end` in code points, and (from `bookText`) its `text`. A SHA-256 `hash` of the whole text changes only when the text does. EPUBs, AZW3 and MOBI through Calibre's conversion (asked first), and PDFs laid out, with their outline as chapters. - `status()`: whether Calibre's Content Server and the other providers answer, and what the library holds. - A `BookRef` wherever a book is named: `listBooks`, `bookFile`, `pdfText`, `shelvesOf`, `addToShelf`, `removeFromShelf`, `createShelf`'s `books`, `suggestShelves`, `estimateSummary`, `summarizeBook`. Numbers work as before. `bookFile` and `pdfText` also read a book file in the vault. - New error codes: `needs-conversion`, `declined`, `not-calibre`, `busy`. - `hasMethod(api, name)`, and `getLibraryHub`'s `opts.by` and `opts.for` to name your plugin, and what it needs books for, in its messages. - `LIBRARY_HUB_TOOLS` is also at `@insurgently/library-hub-api/tools`. Import it from there: at the root it's deprecated, and goes at 1.0. - New error code `needs-topic-map`: the call works from what books are about (book vectors), which Library Hub no longer keeps; the Topic Map plugin serves it. - **Changed:** `suggestShelves` is served by Topic Map: with Topic Map's `rankGroups` it ranks the shelves by what the book and their books are about (`score` and `match` are Topic Map's score, `because` says it in words); without Topic Map, or with one that doesn't offer `rankGroups` yet, it rejects with `needs-topic-map` after checking its arguments, and with `no-vectors` when Topic Map hasn't indexed the book. The `library-hub.suggest_shelves` tool is offered to Model Hub only while Topic Map serves it. - **Changed:** `shelfFromDescription` and `estimateShelfFromDescription` find the books to read by their words: those that best match the description and its terms (title, authors and context, by a keyword ranking), and any naming a term. Before, the books nearest it by meaning. Neither rejects with `no-vectors`. With Topic Map's `rankGroups` there, the books it finds closest to the description in meaning are read too (Topic Map embeds the description); without it, no embedding is asked. - **Changed:** the shelf keys have new names. A shelf note's id is `shelf_id` (was `collection_id`); a book note's shelves are in `shelves` and its exclusions in `shelves_exclude` (were `collections` and `collections_exclude`); `clusters` and `auto_shelves` are gone, and rule shelves with them: the plugin moves old notes once, putting every book a rule shelf held in `shelves`. `listShelves`, `shelfMembers`, `shelvesOf` and the write operations name shelves by `shelf_id` as before and keep the keys to themselves; a plugin that reads book notes' frontmatter itself reads the new keys. - **Changed:** `shelfBaseQuery` gives `list(shelves).contains(…)` and the `shelves_exclude` test, with no rule and no older keys. - **Changed:** `createShelf` with a `rule` rejects with `needs-topic-map`. `NewShelf.rule` and `ShelfInfo.rule` are deprecated (`rule` is never set), until 1.0. - **Changed:** `no-vectors` now means Topic Map hasn't indexed the library, or the book, yet. - **Changed:** `pdfText` lays the page out: a line on its own line, a wider gap a blank line, a word broken at a line's end joined. Before, a page's pieces were joined by spaces. `chars a-b` offsets count in the new text. - The names from before the rename (`getBooks`, `onBooksReady`, `BooksApi`, `BooksErrorCode`, `BooksTool`, `GetBooksResult` and the `BOOKS_*` constants) still work, deprecated. Within 0.x the API only adds and deprecates; they go at 1.0. ## 0.7.0 and before Not published as a package. Plugins copied `api/index.ts` from the repo; the plugin's own CHANGELOG lists what each version added. # README.md Source: https://insurgently.com/projects/library-hub/technical/agents/README.md Types and a few small helpers for reading books from [Library Hub](https://github.com/insurgently/library-hub) in your own Obsidian plugin. Library Hub keeps a book collection in a vault: Calibre and other providers, book notes, shelves, a reader. Its API lets another plugin list books and shelves, get a book's file or its text, open the reader at a passage, and change shelves with undo. This package is that API's contract: the types, the version it describes, and the code that finds the plugin. It has no dependencies and imports nothing at runtime. Your bundler keeps what you use. ## Install ```sh npm install --save-dev @insurgently/library-hub-api ``` Use a tilde, so you take patch fixes only, and raise the minor when you're ready for what it adds. ## Quick look ```ts import { getLibraryHub, onLibraryHubReady, hasMethod, type LibraryHubApi } from '@insurgently/library-hub-api'; const WANTED = '0.8.0'; export default class MyPlugin extends Plugin { libraryHub: LibraryHubApi | null = null; onload() { // now if Library Hub is loaded, and again each time it loads (the user can turn it off and on) onLibraryHubReady(this.app, this, WANTED, (api) => (this.libraryHub = api)); } async chapters(book: string) { const found = getLibraryHub(this.app, WANTED, { by: 'My Plugin', for: 'for its reading lists' }); if (!found.libraryHub) return new Notice(found.message); const text = await found.libraryHub.bookText(book); // a Calibre id, a note, a file, an ISBN… return text.chapters.map((c) => `${c.n}. ${c.title} (${c.location})`); } } ``` A chapter's `location` is what `openBook` takes, so you can send the reader back to it later: ```ts await libraryHub.openBook(book, { location: chapter.location, quote: 'a sentence from the passage' }); ``` ## How it behaves - **Versions.** `getLibraryHub(app, wanted)` checks the installed API against the version you built for: same major, and a minor at least as new. Gate on the lowest minor you need. A method newer than your gate says `@since` in its comment: check it with `hasMethod(api, 'bookText')` first. - **Reloads.** Library Hub fires `library-hub:ready` each time it loads. `onLibraryHubReady` listens for it through your plugin, so the listener goes when your plugin unloads. - **Errors.** Every method rejects with an `Error` whose `code` is a `LibraryHubErrorCode` (`server-off`, `book-missing`, `needs-conversion`…) and whose message says, in plain words, what went wrong and what to do. Branch on the code. Show the message. - **Books.** A `BookRef` names a book: a Calibre id, a note's path or name, a book file, an ISBN, a Calibre uuid, or a provider's id. Numbers work wherever they did before. - **Text.** `bookText` gives a book's chapters with their text, a `location` for each, offsets in code points, and a hash of the whole text that changes only when the text does. `bookChapters` is the same without the text. Converting an AZW3 or MOBI adds a file to the user's Calibre library, so it only happens when you pass `{ convert: true }`, and the user is asked first. - **Tools.** The table of Library Hub's operations as model tools is at `@insurgently/library-hub-api/tools`. Library Hub registers them itself; you only need it to map them somewhere else. - **Old names.** `getBooks`, `onBooksReady`, `BooksApi` and the `BOOKS_*` constants, from before the plugin was renamed, still work and are deprecated. Deprecated names go at 1.0, not before. The full reference is `dist/index.d.ts`, with a comment on every declaration. ## Versions The package's version is the API's version. It's published after the Library Hub release that serves it, so you can't build against an API nobody has yet. [CHANGELOG.md](https://insurgently.com/projects/library-hub/technical/agents/CHANGELOG.md) says which release that is. MIT licensed.