historio-kindle-scribe-notes-parser
v0.2.0
Published
Parses Kindle Scribe notebook export PDFs into structured highlight and note records
Readme
kindle-scribe-notes-parser
Parses a Kindle Scribe "notebook export" PDF (Share → email PDF from the device) into structured book metadata and highlight/note records.
import { parseNotebook } from "historio-kindle-scribe-notes-parser";
import { readFile } from "node:fs/promises";
const pdfBytes = await readFile("my-book-notebook.pdf");
const { book, highlights } = parseNotebook(pdfBytes);
book; // { title, author, asin }
highlights; // HighlightRecord[]Handwriting transcription
transcribeNotes turns handwritten note images (as returned by parseNotebook)
into text via vision models, routed through Vercel AI
Gateway — one gateway key authenticates
every provider the function calls, so there's no per-provider (Anthropic,
Google, ...) credential to manage. It's a separate, stateless function — it
doesn't call parseNotebook for you and never reads credentials from
process.env.
import { transcribeNotes } from "historio-kindle-scribe-notes-parser";
const results = await transcribeNotes(
[{ id: "highlight-4", image: noteImageBytes }],
alreadyTranscribedIds, // ids to skip re-transcribing, e.g. from your own DB
{ gatewayApiKey: process.env.AI_GATEWAY_API_KEY },
);
results; // [{ id, transcription, confidence, isDiagram, model }]All not-skipped images are sent to google/gemini-3.7-flash (via the gateway)
in a single multi-image request. Any result whose confidence falls below
confidenceThreshold (default 0.7) is re-transcribed in one follow-up
anthropic/claude-sonnet-5 request and replaces the Gemini result. Pass a
pre-constructed Vercel AI SDK model via options.model instead of
gatewayApiKey (e.g. for tests, using ai/test's mock model) — it's used for
both the primary and any escalation call.
Pass onProgress to observe the two batched calls as they happen (e.g. to log
progress in a CLI) — it fires at the start and end of the primary call, and,
only if anything escalates, at the start and end of the escalation call:
await transcribeNotes(notes, alreadyTranscribedIds, {
gatewayApiKey: process.env.AI_GATEWAY_API_KEY,
onProgress: (event) => console.error(event),
// { stage: 'primary', phase: 'start', model: 'google/gemini-3.7-flash', count: 24 }
// { stage: 'primary', phase: 'done', model: 'google/gemini-3.7-flash', count: 24, escalatedCount: 3 }
// { stage: 'escalation', phase: 'start', model: 'anthropic/claude-sonnet-5', count: 3 }
// { stage: 'escalation', phase: 'done', model: 'anthropic/claude-sonnet-5', count: 3 }
});CLI
Installing the package also gives you a kindle-scribe-parse command:
npx kindle-scribe-parse my-book-notebook.pdfBy default this prints a human-readable summary of the book and its highlights
to stdout, and always writes two things to
./kindle-scribe-parse/output/<pdf-name>/, relative to your current directory:
a highlights.json file (book metadata + every highlight, with OCR fields
folded in when --ocr is used) and, unless deleted (see --ocr below), any
handwritten note images as PNGs.
Usage: kindle-scribe-parse <pdf-path> [options]
Options:
-o, --out <dir> Directory to write handwritten note images and
highlights.json to
(default: "./kindle-scribe-parse/output/<pdf-name>")
--json Also print the full parsed result as JSON to stdout
(it's always written to highlights.json in the output
directory regardless of this flag). Handwritten note
images are still written to disk (unless deleted, see
--ocr); the JSON references their path rather than
embedding raw image bytes.
--ocr Transcribe handwritten notes into text via vision models,
routed through Vercel AI Gateway (Gemini 3.7 Flash,
escalating low-confidence results to Claude Sonnet).
Requires the AI_GATEWAY_API_KEY environment variable.
Handwritten note images are NOT written to disk unless
--keep-images is also given. Progress is logged to
stderr as each batched model call starts and finishes.
--keep-images Write handwritten note images to disk even when --ocr is
used. Images are always written when --ocr is not given,
so this flag has no effect in that case.
-h, --help Show this help messageExamples:
# Write note images to a specific directory
npx kindle-scribe-parse my-book-notebook.pdf -o ./notes
# Get structured JSON (e.g. to pipe into another tool)
npx kindle-scribe-parse my-book-notebook.pdf --json > notebook.json
# Transcribe handwritten notes to text; deletes note images by default once
# they've been transcribed (nothing is written to -o at all in this mode)
AI_GATEWAY_API_KEY=... \
npx kindle-scribe-parse my-book-notebook.pdf --ocr --json > notebook.json
# Same, but keep the handwritten note images on disk too
AI_GATEWAY_API_KEY=... \
npx kindle-scribe-parse my-book-notebook.pdf --ocr --keep-images -o ./notesAI_GATEWAY_API_KEY doesn't have to be exported in your shell — the CLI loads
a .env file from the current directory automatically (via dotenv), so a
.env containing AI_GATEWAY_API_KEY=... works too. A variable already set
in the environment always wins over the .env file.
During local development (without building/installing first), run it via:
npm run cli -- my-book-notebook.pdfWhat it does
- Extracts book
title,author, andasinfrom the notebook's header page. - Returns each highlight as a
HighlightRecord:chapter(nullable),page,text,date, and an optionalnote. - A
noteis either{ type: 'typed', text }or{ type: 'handwritten', image, width, height }(the image is PNG bytes). - Merges
Highlight Continuedblocks into the preceding highlight as one record. - Recognizes and discards
Bookmark (Blue)entries and standaloneNoteentries (annotations not attached to a highlight) — neither produces an output record. - The CLI's
--ocrflag runs every handwritten note throughtranscribeNotesand addstranscription,confidence,isDiagram, andmodelto each handwritten note's output.
What it doesn't do
parseNotebookdoes no OCR — handwritten notes come back as raw image bytes;transcribeNotes(above) is a separate opt-in step.- No deduplication against previously-parsed notebooks (
transcribeNotesskips re-transcription given analreadyTranscribedIdsset, but computing that set is the caller's responsibility). - No support for PDF exports other than the Kindle Scribe notebook format.
License and the mupdf dependency
This package is published under AGPL-3.0-only, inherited from its PDF engine:
mupdf (Artifex Software's WASM build of
MuPDF), which is licensed under the GNU Affero General Public License v3.0.
If you embed this package in your own product, the AGPL's copyleft terms
apply to your product as a whole — most notably, the AGPL's network-use clause
extends the usual GPL copyleft to software made available over a network (e.g.
a hosted web app), not just distributed binaries. Read the
AGPL-3.0 license text and get your
own legal advice before shipping this in a closed-source or commercial product.
Artifex also offers commercial licenses for mupdf if AGPL terms don't work for
your use case.
Note that if this license agreement is not what you want, consider pdfjs-dist (Apache-2.0).
Development
npm install
npm run build
npm testRequires no Python runtime or subprocess — pure JS/TS plus the mupdf WASM
dependency.
Releasing
Publishing is manual — there's no CI-triggered publish step. From a clean,
up-to-date main:
npm version patch # or minor / major — runs typecheck+test, bumps the
# version, commits, tags, and pushes both (preversion/
# postversion hooks in package.json)
npm publish # builds dist/ via prepublishOnly, then publishesnpm login once beforehand if you haven't authenticated with npm on this
machine.
