Insurgently

@insurgently/library-hub-api

Types and a few small helpers for reading books from Library Hub in your own Obsidian plugin.

agents/README.md
title: "@insurgently/library-hub-api"
built: 2026-10-30
source: api/README.md
html: https://insurgently.com/projects/library-hub/technical/agents/README
Index of these files: llms.txt

Types and a few small helpers for reading books from 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

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

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:

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 says which release that is.

MIT licensed.