Skip to main content

Ebooks

@svendowideit/ebooksv2026.10.23.1· MIT· 1d agoBEHAVIORSWORKFLOWSACCEPTED WARNINGS
01README

Find every ebook on your machine, detect its bibliographic metadata, resolve authors and titles against Wikipedia + Wikidata, and render a browsable HTML page linking each file to its location on disk. WHAT IT DOES

Turns a filesystem full of ebooks into a catalogued, browsable library. It walks the tree for ebook files, derives author/title/ISBN/publish dates from the path, filename and file contents, then resolves each author and title against Wikipedia and Wikidata so the rendered pages show canonical names — and only links names actually classified as people. The scan is resumable: each run continues from where the last stopped and stops itself after a five-minute budget, so a million-file home directory is catalogued across several runs without ever re-walking ground already covered. It fetches nothing itself — all network I/O is delegated (transitively) to @svendowideit/web-cache, which paces and caches, so re-analysing an already-resolved name costs no network traffic.

It can also publish those pages over HTTP(S): when @svendowideit/caddy is installed, the workflow's final step declares a Caddy file_server route serving the render directory at ebooks.<host>.<domain>, reading the host and domain from the caddy model that already owns this machine. The route lives on a dedicated ebooks-caddy model that caddy merges with the models it already runs, and caddy derives the A record and certificate for the name — so nothing is hardcoded and nothing is duplicated. With caddy absent the step skips with a log and the pages remain on disk.

INSTALL

swamp extension pull @svendowideit/ebooks

DEPENDENCIES

@svendowideit/web-cache, @svendowideit/wikipedia, and @svendowideit/wikidata, declared in this manifest's dependencies: — the single swamp extension pull above installs them automatically. Nothing is installed on the host.

@svendowideit/caddy is an optional dependency, declared in this manifest's optionalDependencies:. It is not installed for you: pull it separately to enable the HTTP(S) serving step, which is skipped when it is absent.

RUN

# Scan, detect, resolve, and render — re-run to continue a long scan in
# five-minute increments; the HTML page regenerates each time.
swamp workflow run @svendowideit/ebook-scan

# Point the scan at a specific directory instead of $HOME.
swamp workflow run @svendowideit/ebook-scan --input root=/home/me/books

# Re-detect and re-render the already-found paths without re-walking the
# filesystem — use after changing metadata detection or rendering.
swamp workflow run @svendowideit/ebook-scan --input skipScan=true

# Raise the per-call origin-fetch cap to drain the resolution backlog
# faster (cached hits do not count).
swamp workflow run @svendowideit/ebook-scan --input maxFetches=50

# Read the discovered ebooks and their detected metadata as JSON.
swamp data get ebook-scan state --json

# (Re)declare the Caddy file_server route on its own — normally the last
# step of @svendowideit/ebook-scan does this for you. Needs
# @svendowideit/caddy and a caddy model that already owns this machine; it
# serves the render directory at ebooks.<host>.<domain>.
swamp model method run ebooks ensure-caddy-serve

CONFIGURE

All workflow inputs are optional:

root               Filesystem path to scan          (default ~)
outputPath         HTML listing output path          (default ~/.swamp/ebooks/ebooks.html)
authorsOutputPath  Author-index output path          (default ~/.swamp/ebooks/index.html)
title              Page title                        (default "Ebooks")
cacheDir           Shared web-cache directory        (default ~/.swamp/web-cache)
maxFetches         Origin fetches per get-many call  (default 20)
skipScan           Skip the directory scan           (default false)

The ebooks model also reads global arguments:

extensions           Ebook file extensions to match
excludePatterns      Directory names to skip
cacheDir             Shared web-cache directory
caddyModelName       @svendowideit/caddy model to read the ebooks host and
                     domain from, for ensure-caddy-serve
                     (default: auto-discover the model owning this machine)
caddyRouteHostname   Override the served hostname
                     (default: ebooks.<host>.<domain>)

See the README for values and defaults.

WHAT IT INSTALLS

Nothing on the host — no services, daemons, cron triggers, or webhooks. It reads the filesystem and the shared web-cache and writes swamp data plus the two HTML files named by outputPath and authorsOutputPath.

If @svendowideit/caddy is installed and a caddy model already owns this machine, the final ebook-scan step also declares a Caddy file_server route serving that directory at ebooks.<host>.<domain>, on a dedicated ebooks-caddy model that caddy merges with the models it already runs. Nothing new is installed by ebooks itself — no service, no daemon.

02Behaviors2
@svendowideit/ebooksv2026.10.23.1ebooks.ts

Global Arguments

ArgumentTypeDescription
root?stringFilesystem path to scan for ebooks (default ~)
outputPath?stringFilesystem path for the generated HTML listing
authorsOutputPath?stringFilesystem path for the author-grouped index (only ebooks with a
extensions?arrayFile extensions to treat as ebooks (without the dot)
excludePatterns?arrayDirectory names to skip while scanning
cacheDir?stringShared web-cache directory to read cached Wikipedia/Wikidata responses
caddyModelName?stringName of the @svendowideit/caddy model whose base domain and host record
caddyRouteHostname?stringOverride the full hostname the ebooks pages are served under. Empty
fn scan-disk(maxDurationMs?: number)
Resume (or start) an ebook scan, enumerating directories until the
ArgumentTypeDescription
maxDurationMs?numberSelf-imposed wall-clock budget per scan run (default 5 minutes)
fn detect-metadata(file?: string, maxDurationMs?: number)
Detect bibliographic metadata (author, title, ISBN, publish dates)
ArgumentTypeDescription
file?stringSpecific ebook file to detect metadata for. Omit to detect metadata
maxDurationMs?numberSelf-imposed wall-clock budget per detect run (default 5 minutes)
fn resolve-wikipedia()
DEPRECATED — kept for back-compat. Resolution is now a per-name
fn plan-resolution(maxNames?: number)
Compute the set of author names and book titles that need resolving
ArgumentTypeDescription
maxNames?numberCap on the number of names emitted this run. Already-resolved names are
fn pick-candidate(name: string, expectKind: enum, key?: string, results?: array, pages?: record)
Pick one name's canonical candidate from its already-parsed search +
ArgumentTypeDescription
namestring
expectKindenum
key?stringFilesystem-safe instance key for this name (defaults to nameKey(name))
results?array
pages?record
fn classify(name: string, expectKind: enum, key?: string, title?: string, url?: string, description?: string, wikidataId?: string, infoboxTemplate?: string, instanceOf?: array)
Classify one name (author vs book) and write the final `resolution`
ArgumentTypeDescription
namestring
expectKindenum
key?stringFilesystem-safe instance key for this name (defaults to nameKey(name))
title?string
url?string
description?string
wikidataId?string
infoboxTemplate?string
instanceOf?array
fn render-html-list(title?: string)
Render the discovered ebooks into an HTML page at the configured outputPath.
ArgumentTypeDescription
title?stringPage title
fn render-html-authors(title?: string)
Render an author-grouped index page (only ebooks with a detected
ArgumentTypeDescription
title?stringPage title
fn ensure-caddy-serve(model?: string, serveModel?: string, hostname?: string, serveRoot?: string)
When @svendowideit/caddy is installed, declare a Caddy file_server
ArgumentTypeDescription
model?stringSource @svendowideit/caddy model whose base domain and host record supply
serveModel?string
hostname?stringFull hostname to serve the pages on. Defaults to the global
serveRoot?stringDirectory to serve over HTTP(S). Defaults to the directory of the

Resources

state(infinite)— Resumable scan state (frontier, seen dirs, discovered ebooks)
metadata(infinite)— Detected book metadata keyed by ebook path (shared shape with
names(infinite)— The set of names that need resolving (emitted by plan-resolution)
resolution(infinite)— A per-name Wikipedia/Wikidata resolution record (factory: one instance
candidate(infinite)— A chosen candidate (the pick-candidate output, per name)
page(infinite)— Result of the last HTML page generation
caddyServe(infinite)— Status of the Caddy file_server route that serves the rendered pages
@svendowideit/book-metadatav2026.10.23.1book_metadata.ts
fn detect(file: string)
Detect book metadata from a file (path/filename, file metadata,
ArgumentTypeDescription
filestringPath to the book file to detect metadata from
fn register(title?: string, author?: string, isbn?: string, publishedAt?: string, editionPublishedAt?: string, publisher?: string, language?: string, description?: string, series?: string, sourcePath?: string, sourceName?: string)
Store bibliographic metadata you already have (e.g. for a physical book).
ArgumentTypeDescription
title?string
author?string
isbn?string
publishedAt?string
editionPublishedAt?string
publisher?string
language?string
description?string
series?string
sourcePath?stringLink back to a source file/record
sourceName?string

Resources

book(infinite)— Detected or registered bibliographic metadata for a book
03Workflows3
@svendowideit/ebook-scanbbb60144-2c5f-4c95-8e0a-a89fe33aac01

Incrementally scan the local filesystem for ebooks, detect bibliographic metadata, resolve authors/titles against Wikipedia/Wikidata, and render an HTML page linking to each file's location on disk. The scan-disk step is resumable and self-terminating: each run enumerates directories for up to 5 minutes, then hands off to the next step. Re-run the workflow to continue scanning from where it left off (state is persisted in swamp data). Once the whole tree is covered, the scan restarts fresh. Re

mainScan for ebooks, resolve authors/titles, then (re)generate the HTML listing.
1.scan-disk— Resume (or start) the ebook scan, up to 5 minutes per run.
2.detect-metadata— Detect book metadata (author, title, ISBN, dates) for the discovered ebooks.
3.plan-resolution— Compute the names (authors + titles) that need resolving.
4.resolve-names— Resolve + classify each name via the fan-out child workflow.
5.render-html-list— Render discovered ebooks (with detected metadata) into an HTML page.
6.render-html-authors— Render an author-grouped index page for ebooks with a detected author.
7.serve-html— When @svendowideit/caddy is installed, declare a Caddy file_server route serving the rendered directory at ebooks.<host>.<domain>. The host and domain are read from the caddy model that already owns this machine; the route lives on a dedicated ebooks-caddy model that caddy merges with the models it already runs, and caddy derives the A record and certificate for the name. Skips with a log when caddy is not installed or no single caddy model can be chosen.
@svendowideit/ebooks-resolve-nameaaa8740f-122c-4dae-9f58-bdcb6028f434

Resolve and classify one name (an author or a book title) against Wikipedia and Wikidata, through the shared cache, transparently. Emits a single per-name `resolution` record owned by @svendowideit/ebooks. The whole pipeline is workflow composition — this workflow knows nothing about fetching or caching. It calls the wikipedia/wikidata workflows, which in turn call web-cache; cache-hit vs fetch is invisible here (only surfaced as metadata). wikipedia-search → candidate titles wikipedi

mainResolve + classify one name.
1.search— Search Wikipedia for the name.
2.page-props— Resolve candidate titles to canonical title/QID.
3.pick— Choose the best candidate for the name.
4.infobox— Fetch + parse the chosen page's infobox.
5.instance-of— Fetch + parse the chosen entity's P31 values.
6.classify— Classify (author/book) and write the resolution record.
@svendowideit/ebooks-resolve-namesd36e854a-94ce-4427-a6b2-42b78b7aabcc

Fan out over a list of names (authors + book titles), resolving and classifying each via @svendowideit/ebooks-resolve-name. This is the typed boundary between the parent (which computes the name list) and the per-name resolution pipeline — the list is passed as a typed input, so forEach expands only after the parent's plan-resolution step has completed. Inputs: items — array of { name, expectKind, key } objects to resolve cacheDir — shared cache dir (default ~/.swamp/web-cache)

mainResolve + classify each name.
1.resolve-${{ self.item.key }}
04Previous Versions4
2026.10.18.2MIT
2026.10.18.1MIT(was none)

Modified 1 behaviors

2026.09.24.1
2026.09.20.2

Modified 1 behaviors. Added 2 workflows. updated dependencies

05Stats
A
93 / 100
Downloads
29
Archive size
70.6 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit1/2partial
    • @std/path--jsr:@std/path@1 is a range; audited 1.1.6, the version the registry resolved
    2 dependencies found
    • jsr:@std/path@1 -- audited 1.1.6
    • npm:fflate@0.8.3 -- audited 0.8.3
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License2/2earned
  • Verified public repository2/2earned

07Platforms