Grimoire 1.1.0: A Local-First Bookmark Manager Built as a Bun Daemon and a React Frontend
Bookmark manager for the wizards 🧙
At a glance
- What is it?
- Grimoire stores bookmarks, extracted page text and search indexes in a local SQLite database behind a loopback-bound Bun daemon, with optional AI enrichment. The design is sound for self-hosters who want their reading archive on their own disk, but the v1 rewrite means migration from the SvelteKit-era v0.5 line is still labelled experimental.
- Who is it for?
- Adopt Grimoire if you self-host, are comfortable running a Bun daemon on 127.0.0.1:3210, and want bookmarks, extracted text and embeddings in one SQLite file you can copy. Do not adopt it if you need a supported Homebrew install, or if you are migrating a large v0.5 library and cannot tolerate an experimental migration tool.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem Grimoire Targets: Bookmarks You Cannot Find Again
Saving a link is easy. Retrieving it two years later, when you remember the argument but not the URL, is the part that fails. Grimoire is aimed at people who accumulate technical resources: documentation pages, GitHub issues, StackOverflow answers, PDFs, conference talks on YouTube. The README frames the audience directly, describing a bookmark manager for people who save technical resources and need to find them later. That is a narrower group than general bookmarking. A recipe collector does not need transcript extraction or embedding search. Someone with four hundred saved GitHub issues does. The project is MIT licensed, TypeScript and Bun on the daemon side, React 18 with Vite and Tailwind on the front. It is not archived, and the last push recorded is 2026-09-09, with v1.1.0 released on 2026-08-25.
Two Processes, One SQLite File, and a Loopback Port
Grimoire is not a single binary. According to the README, it has two runtime parts. The frontend lives under src/ and is React 18, Vite, TypeScript, Tailwind CSS and Radix UI primitives. The daemon lives under daemon/ and is Bun, Hono and SQLite, listening on 127.0.0.1:3210 by default. In a source checkout you run them separately and the Vite app on port 8080 talks to the daemon on 3210. Under Docker, one loopback-bound port serves both the built frontend and the API. That split is the main architectural decision, and it has consequences. The daemon owns the database and the job queue. The frontend is a client. You can point a browser at the daemon without running Vite, which is what the Docker path does. You can also run the daemon headless and drive it from the API, the MCP integration, or the browser bookmarklet the README lists as save sources. Native installs put everything under ~/.local/share/littleimp/, including littleimp.db, a .env of install-time defaults, the built frontend in dist/, backups/, restore-rollbacks/ and logs/. The directory name littleimp, rather than grimoire, is a leftover from the project's earlier naming and worth knowing before you go looking for a grimoire folder.
Progressive Ingestion: The Bookmark Appears Before the Pipeline Finishes
The README gives the ingestion flow as a text diagram: save URL, enqueue a durable SQLite job, fetch public content, extract readable text and metadata, enrich with optional AI summary, tags and category, create optional embeddings, then update search indexes. Two details in that sequence matter more than the rest. First, the job is durable and stored in SQLite, so a daemon restart does not silently drop a queued bookmark. Second, the README states that bookmarks are visible immediately after save, and that pipeline failures keep the bookmark usable and expose retry and reprocess controls. This is the correct trade-off for a personal tool. A failed fetch of a paywalled page should not block you from saving the link and tagging it by hand. The extraction targets are listed explicitly: normal web pages, PDFs, GitHub repositories, GitHub issues, StackOverflow and StackExchange pages, and YouTube metadata and transcripts where available. The phrase where available is doing real work there. Transcript extraction depends on YouTube exposing captions, and the README does not promise it universally. Search runs on SQLite FTS5 for keywords, on embeddings for meaning, or in a hybrid ranking mode. The README does not document how the hybrid score is combined, so treat that mode as something to evaluate against your own library rather than something whose ranking you can reason about from the docs.
Getting It Running: Docker, Source Checkout, and the Installer
The fastest path is Docker. The README gives three commands: docker compose up -d, then curl http://127.0.0.1:3210/health, then open http://127.0.0.1:3210 in a browser. The health endpoint is the thing to check, because a container that is up but has not finished initialising SQLite will still accept the port. For development, the README recommends a source checkout and calls it the path that works from a public clone without depending on release assets. Clone the repository, run npm install at the root, then cd daemon && bun install, then start npm run daemon:dev and npm run dev in separate terminals. The Vite app comes up on http://127.0.0.1:8080 and talks to the daemon on 3210. There is also a native installer under daemon/: ./install.sh copies daemon files, installs production dependencies, builds the frontend, writes a default config, registers a user service and starts the daemon. Upgrades use ./install.sh --upgrade. Uninstall preserves data by default; ./install.sh --uninstall --purge is the flag that removes the local library, and the README is explicit that purge is only for when you intend to delete it. If you want to look at the interface before committing to any of this, the hosted demo at goniszewski.com/grimoire/demo/ is a static client-only profile with synthetic fixtures and an in-memory API in the browser. It does not start the daemon, create an account, or persist anything. You can run the same thing locally with npm run build:demo and npm run preview:demo on port 4173.
The Homebrew Formula Is Not a Supported Install Path Yet
The README is unusually candid here, and it deserves repeating rather than glossing over. A Homebrew formula exists in the repository, but public install, service lifecycle and data-preservation checks have not passed against release assets. The README states plainly that it is not a supported installation path yet and labels it pending live validation. If you are on macOS and reached for brew first, the answer from the project's own documentation is no. Use the source installer or Docker. This is the kind of honesty that is worth crediting, because the alternative is a formula that half-works and generates issues the maintainer has to triage. It also means anyone evaluating Grimoire for a team should treat the supported matrix as two paths, not three, until the formula passes validation.
The v0.5 Migration Is Experimental, and the Legacy App Is a Different Codebase
Grimoire 1.x is a complete rewrite. The README says so directly and points to the legacy/v0.x branch, which holds the SvelteKit-based v0.5.x line. That has two implications. The first is that the current application shares a name and a purpose with the old one but not a codebase, so bug reports, configuration habits and mental models from v0.5 do not carry over cleanly. The second is the migration tool. Grimoire 1.1.0 includes an experimental littleimp migrate command for v0.5 SQLite data, and the README tells you to read docs/migration.md before applying it. Experimental, in a project that otherwise documents its install and uninstall flags precisely, is a meaningful label. The safe pattern is to copy your v0.5 database, run the migration against the copy, and inspect the result before letting it near your only copy of years of bookmarks. The README does not state whether the migration is reversible, so assume it is not. If your v0.5 library is large or contains hand-curated notes you cannot reconstruct, waiting for the migration to lose the experimental label costs you nothing except time on the old branch.
AI Enrichment Is Optional, and the Provider List Is the Point
Grimoire runs without AI providers, and the README lists the optional ones: OpenAI, Ollama, Anthropic, OpenRouter, DeepSeek international, and custom OpenAI-compatible chat or embeddings endpoints. The inclusion of Ollama is the interesting entry for anyone whose reason for self-hosting is privacy. Summaries, auto-tagging, auto-categorisation and embeddings are the features that would otherwise push you toward a hosted service, and Ollama keeps the whole loop on your machine. The custom OpenAI-compatible endpoint option covers the middle ground, letting you point at a self-hosted inference server that speaks the same API. What the README does not give is any guidance on model choice, embedding dimension, or the cost of re-embedding a large library after switching embedding models. That gap is real. If you start with one embedding model and later change it, the existing vectors and the new query vectors will not be comparable, and the documentation supplied here says nothing about a re-embedding command. Decide on your embedding provider before you import a large library, not after.
Privacy Boundaries, and Where the Guarantees Stop
Native installs keep data under ~/.local/share/littleimp/, and the daemon binds to 127.0.0.1:3210 by default, which means it is not reachable from other machines unless you deliberately change that. The README describes the project as local-first, private and fully open source under MIT. That claim holds for the storage layer: bookmarks, content, tags, categories, jobs, notes, timeline events, backups and settings all live in SQLite and local files. It does not hold unconditionally for ingestion. Fetching a public URL means your daemon makes an outbound request to that URL, and enabling a hosted AI provider means page content leaves your machine for that provider. The README lists both behaviours as features rather than caveats, which is fair, but the privacy guarantee is about where your library is stored, not about whether anything ever leaves the host. Read it that way and there is no contradiction. Read it as an airgap and you will be surprised the first time a summary request hits OpenAI. The backups/ and restore-rollbacks/ directories are also worth understanding before you restore anything, since the presence of pre-restore rollback copies implies the restore operation is treated as something that can go wrong.
How Grimoire Differs From a Hosted Read-It-Later Service
The obvious comparison is a hosted read-it-later service, and the difference is not the feature list. It is where the database lives and who can change the terms. A hosted service gives you a sync layer, mobile apps and someone else's uptime in exchange for your reading history sitting on their infrastructure under a policy they can revise. Grimoire gives you littleimp.db, a file you can copy, back up and inspect with the sqlite3 CLI, in exchange for running and updating a daemon yourself. The trade is explicit and the README does not pretend otherwise. The second comparison worth drawing is against a plain browser bookmark store with full-text search. Browser bookmarks are already local, already synced by the browser vendor, and cost nothing to maintain. What they do not do is extract readable text from a PDF, pull a YouTube transcript, or rank results by embedding similarity. If your saved links are mostly pages you will recognise by title, the browser is the cheaper tool and Grimoire is overhead. Grimoire earns its place when the content behind the link is what you need to search, not the title.
Maintenance Cost, Upgrade Path, and What the Licence Does Not Cover
The upgrade story is ./install.sh --upgrade from an unpacked source checkout or release archive. That means you are tracking the repository, not a package manager, unless the Homebrew formula eventually passes validation. Practically, each upgrade is a rebuild of the frontend and a restart of the user service, and the README states that uninstall preserves data unless you pass --purge, which is the property that makes upgrades low-risk for the database. The local update check feature listed under Grimoire's capabilities suggests the app will tell you when a newer version exists, though the README does not describe how that check is performed or whether it contacts a remote endpoint. That is a question to answer before you run it on a machine with strict egress rules. On licensing: the project is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is the extent of what can be said from the material here. Whether MIT is the right licence for your organisation's policy on bundled AI provider SDKs, or on the Docker image's base layers, is a question for your own review, and nothing in this repository's documentation answers it.
Editorial conclusion
Adopt Grimoire if you self-host, are comfortable running a Bun daemon on 127.0.0.1:3210, and want bookmarks, extracted text and embeddings in one SQLite file you can copy. Do not adopt it if you need a supported Homebrew install, or if you are migrating a large v0.5 library and cannot tolerate an experimental migration tool. Verify first that ./install.sh --upgrade preserves your data directory, and run littleimp migrate on a copy of littleimp.db before touching the original.
Community notes