Infinity Notes is a small TypeScript monorepo for publishing interconnected markdown notes as a browsable knowledge graph.
It has three runtime parts:
@infinity-notes/note-processorparses markdown notes, wiki links, backlinks, note previews, concepts, and thread breadcrumbs.@infinity-notes/workerserves the note API from Cloudflare Workers and R2.@infinity-notes/frontendrenders the notes in a Vite React app.
The create-infinity-notes CLI creates a book content directory with metadata,
starter notes, and an upload script.
Install dependencies and verify the workspace:
pnpm install
pnpm verifyBuild all packages:
pnpm buildRun the test suite:
pnpm testRun the CLI without installing it:
npx create-infinity-notes my-bookInstall it globally if you want a reusable local command:
npm install -g create-infinity-notes
create-infinity-notes my-bookBoth entrypoints start the same interactive scaffold. The first positional argument seeds the default book id and the output directory name. The CLI then prompts for:
- book title
- book id
- authors
- description
- R2 key prefix
Example session:
$ npx create-infinity-notes my-book
│
◇ Book title?
│ My Book
◇ Book id?
│ my-book
◇ Authors? (comma-separated)
│ Author One, Author Two
◇ Book description?
│ A networked note set
◇ R2 key prefix?
│ books
│
└ Done! Next steps:
cd my-book
edit notes/
./upload.sh my-book
It creates a directory like this:
my-book/
meta.json
notes/
Welcome.md
About.md
concepts/
.gitkeep
threads/
.gitkeep
upload.sh
wrangler.toml
README.md
After scaffolding:
cd my-book- Edit the markdown files under
notes/ - Upload the book content with
./upload.sh my-book
The generated README.md inside the book directory repeats the upload basics,
and upload.sh uses the scaffolded book id by default. Passing the book id
explicitly keeps the command obvious when you are scripting or sharing steps.
To publish the book content to R2 and trigger an index rebuild:
cd my-book
INFINITY_NOTES_BUCKET=your-r2-bucket \
INFINITY_NOTES_R2_PREFIX=books \
INFINITY_NOTES_WORKER_URL=https://your-worker.workers.dev \
./upload.sh my-bookThe upload script writes meta.json and every notes/**/*.md file to R2, then
calls the worker rebuild endpoint for that book. You can override:
INFINITY_NOTES_BUCKETfor the target R2 bucket nameINFINITY_NOTES_R2_PREFIXfor the root key prefix, such asbooksorstaging/booksINFINITY_NOTES_WORKER_URLfor the deployed worker base URL
Notes are markdown files. The filename, relative to notes/, becomes the note
path. For example, notes/concepts/Feedback Loop.md becomes
concepts/Feedback Loop.
Use front matter for metadata:
---
title: Feedback Loop
snippet: A cycle where outputs influence future inputs.
type: concept
---
# Feedback Loop
A feedback loop connects outputs back into future inputs.
See [[Compounding]] and [[Small Notes Become Systems]].Supported front matter:
title: display title; defaults to the note path.snippet: short preview text; defaults to the first non-heading lines.type: optionalconceptorthread.parent: parent note path or title, used for thread breadcrumbs.source_chapter: optional numeric chapter path, such as[1, 2].
Link between notes with double brackets:
This note references [[Feedback Loop]].The processor resolves links against note paths and titles, builds backlinks, and emits hydrated note JSON for the frontend.
Run the worker API:
pnpm --filter @infinity-notes/worker devRun the frontend:
pnpm --filter @infinity-notes/frontend devThe frontend dev server proxies /api requests to http://localhost:8787.
For production builds, set VITE_API_URL if you need to override the default
worker API URL:
VITE_API_URL=https://your-worker.workers.dev/api pnpm --filter @infinity-notes/frontend buildpackages/
create-infinity-notes/ CLI for creating book content directories
note-processor/ Markdown parsing, links, backlinks, previews, indexing
worker/ Hono API for Cloudflare Workers and R2
frontend/ Vite React frontend
Useful package commands:
pnpm --filter create-infinity-notes build
pnpm --filter @infinity-notes/note-processor test
pnpm --filter @infinity-notes/worker build
pnpm --filter @infinity-notes/frontend buildThe worker exposes:
GET /api/booksGET /api/books/:bookId/previewsGET /api/books/:bookId/conceptsGET /api/books/:bookId/note/*POST /api/books/:bookId/rebuild
Book content is stored under:
<R2_PREFIX>/<bookId>/meta.json
<R2_PREFIX>/<bookId>/notes/**/*.md
Rebuilding a book reads markdown from R2 and writes generated JSON indexes:
<R2_PREFIX>/<bookId>/_previews.json
<R2_PREFIX>/<bookId>/_notes/<notePath>.json
<R2_PREFIX>/_catalog.json
Configure the worker R2 binding in packages/worker/wrangler.toml:
[[r2_buckets]]
binding = "NOTES_BUCKET"
bucket_name = "your-r2-bucket"
[vars]
R2_PREFIX = "books"Deploy the worker with Wrangler:
pnpm --filter @infinity-notes/worker build
cd packages/worker
pnpm exec wrangler deployBuild the frontend with the deployed worker API URL:
VITE_API_URL=https://your-worker.workers.dev/api pnpm --filter @infinity-notes/frontend buildDeploy packages/frontend/dist with your static hosting provider.
Only sanitized examples are committed to this repository.
examples/demo-basic/contains a small public note graph.- Private, generated, or book-derived examples are intentionally ignored.
Before publishing packages or pushing a public release, run:
pnpm pack:check
pnpm verifypnpm pack:check verifies that:
- only public-safe example files are visible to git;
- every publishable package has a
filesallowlist; - npm package dry-runs do not include examples, local env files, build caches, Wrangler state, or nested dependencies.
Use pnpm for workspace operations. Keep generated content, private examples, local R2 state, and dependency directories out of commits.
For changes that affect package output, run:
pnpm verify