innernet (apps)
From Innerpedia, the encyclopedia of you
innernet is a TypeScript Next.js application within apps, part of euler. Created in September 2026, it was last touched 3 weeks ago. It holds 317 files across 53 folders, 98 MB in all. Of its folders, seven lie past the six levels Innerpedia reads: their files are counted, but they have no pages of their own.
Innernet turns the folders on your machine into a small, connected web. Find a project by its name, language, framework or README text, then open its Innerpedia article to see what it does, how it is organised and how it has changed.
Contents
Overview
Your existing folders are the source material. One local JSON index powers both readers.
| Explore | What you will find |
|---|---|
Search · / and /search | A familiar search box, highlighted results, live suggestions, spelling corrections and project knowledge panels. |
Innerpedia · /wiki | Project articles with README overviews, folder trees, technology, Git history and related projects. Browse categories, statistics or a random article. |
Field guide · /#guide | The second half of the home page: an illustrated walkthrough of the crawl, search and privacy rules, an interactive recipe for turning a folder into an article, and a prompt that hands Innernet to an AI assistant. |
History · /activity | Every page you opened, one session per browser tab, kept as plain files on this machine with a copy in its own database. The header's arrows step back and forward. |
The public demo (Vercel)
The same app runs as a public demo at https://innernet-nine.vercel.app. Instead of this machine, it indexes the open-source repositories of github.com/quirq-ai:
pnpm index:demo # clone the public quirq-ai repos (anonymous HTTPS) and build data/demo/index.json
pnpm dev:demo # preview the demo locally
data/demo/index.json is committed, and every path in it is a GitHub URL. Vercel builds
always run the demo (VERCEL=1); locally INNERNET_DEMO=1 switches it on (lib/mode.ts).
In the demo the localhost guard is off, a banner says what you are looking at, folders
link to GitHub instead of VS Code, and the guide plays the committed 720p film. With a
database it keeps the pages and searches visitors open for 30 days, anonymously
(the demo's history); without one, history stays in the browser.
How it works
flowchart LR
folders["Your folders"] --> crawler["Crawler<br>READMEs, manifests, Git metadata"]
crawler --> index["Local index<br>data/index.json"]
index --> search["Search<br>Find a project"]
index --> wiki["Innerpedia<br>Read its story"]
The crawler records folder structure, project descriptions, languages, dependencies and Git history. Innerpedia assembles articles from that information; folders with less project context become stubs. Shared names get disambiguation pages, and categories connect projects across your directory tree.
Search runs on the server with MiniSearch. The app uses Next.js, React, TypeScript and Tailwind CSS, with Markdown rendered by react-markdown and remark-gfm.
Get started
You need Node.js 20.9 or newer and pnpm. Run the commands from the repository directory so the server can find its index.
1. Install
git clone https://github.com/quirq-ai/innernet.git
cd innernet
pnpm install
2. Choose your folders
Edit innernet.config.json before indexing. The defaults are:
{
"roots": ["~/Programming"],
"maxDepth": 6
}
Add or replace roots with the folders you want to explore. maxDepth controls how many
levels of folders get their own pages.
3. Index and open
pnpm index
pnpm dev
Open localhost:3470, search for a familiar project, or visit
Innerpedia and the field guide.
Both pnpm dev and pnpm start bind to 127.0.0.1.
The first dev run or build needs network access to fetch fonts. Afterward, the fonts are served locally.
Keep the index fresh
Run pnpm index after your folders change. The server picks up the new index on the
next request without a restart.
For a single run, override the configured roots and depth:
INNERNET_ROOTS="~/projects,~/notes" INNERNET_MAX_DEPTH=3 pnpm index
To inspect a trial index without replacing the app's index:
INNERNET_ROOTS="~/projects" INNERNET_OUT=/tmp/innernet-test.json pnpm index
The app always reads data/index.json from its working directory; INNERNET_OUT only
changes where the crawler writes.
Search your folders
Start with ordinary words, then narrow the results with operators. Search covers project names, paths, descriptions, README text, dependencies and agent notes.
| Try | Finds |
|---|---|
lang:rust | Folders whose leading languages include Rust. |
in:experiments | Folders inside a matching ancestor folder. |
kind:repo | Git repositories. |
fw:next | Projects using Next.js. |
is:article | Innerpedia articles. |
is:stub | Folders with less project context. |
Combine them with free text, such as chat lang:ts fw:next. Quote values containing
spaces, such as in:"side projects". Results can also be filtered using the
Projects, Repositories, Documents and Folders tabs.
History
Innernet writes down what you open, in plain files on this machine. The files are the record and the format every app shares, with no index file and no schema registry: a session is a folder, and each app that takes part writes its own JSON Lines file inside it. The database keeps a copy of the lines, read in as they change.
~/.innernet/history/ set INNERNET_HISTORY_DIR to keep it elsewhere
2026-10-03T05-12-07Z_k3f9a2/ one folder per browser tab: its start time (UTC) and a short id
innernet.jsonl one JSON object per line, appended by Innernet
<any-app>.jsonl any other app or tool adds its own file
Every line is {"at":"<ISO time>","app":"<name>","kind":"<verb>", ...} plus whatever
fields the app likes. Innernet writes visit, search, back and forward:
{"at":"2026-10-03T05:12:09.512Z","app":"innernet","kind":"visit","url":"/wiki/galileo","title":"galileo"}
{"at":"2026-10-03T05:12:31.020Z","app":"innernet","kind":"search","q":"agent","url":"/search?q=agent"}
Reading a session means reading every *.jsonl in its folder and sorting by at.
App names are lowercase letters, digits, - and _; lines over 4 KB, lines that are
not JSON and lines without a valid at and kind are skipped. To add your own
activity, append a line to a file named after your app. This joins the newest session:
echo '{"at":"'$(date -u +%FT%TZ)'","app":"notes","kind":"edit","file":"todo.md"}' >> "$(ls -d ~/.innernet/history/*/ | tail -1)notes.jsonl"
/activity, the clock in the header, shows the
sessions newest first, each opening onto its events merged across apps. The header's
arrows step back and forward through the pages of the current tab. The browser writes
through the app's own /api/activity route, which accepts only same-origin requests
on localhost, and browsers driven by automation are not recorded. The route appends the
line to the file first, then reads that session's files into the database; /activity
reads in every folder that changed before it shows the list, so a line another app
appended shows on the next visit. Without the database the page reads the files, as it
always has. Delete a session's folder to forget it; the database forgets it on its next
read. The public
demo keeps its visitors' history differently: see the demo's history.
The database
Innernet keeps a copy of what it serves in a small Postgres database. The files stay the source; the copy means a deleted index does not blank the site, the history page reads every app's lines with one query, and both can be written back out as files.
On this machine it is PGlite, Postgres compiled to WebAssembly, running inside the server on a folder of plain files. Nothing listens on a port and nothing leaves the machine.
~/.innernet/db/ set INNERNET_DB_DIR to keep it elsewhere, INNERNET_DB=off to go without
pgdata/ the Postgres cluster (folder mode 700)
owner.lock the process that has it open: one at a time
| Table | Holds |
|---|---|
kv | Small named values: the index's meta and disambiguation, the schema version, and one mark per history file (bytes read so far, inode, mtime, a hash of the bytes before the mark). |
pages | One row per page of the index, in the index's order. |
activity | One row per history line: session, app, time, kind, the event as JSON and the exact line. Its key is a hash of the session, the app and the line, so a line stored twice is one row. |
The server stores each new data/index.json as it loads it, and if the file goes missing
it serves the stored index instead. The history comes in as it changes: after each event
the recorder sends, and whenever /activity is read. A file whose size, inode and mtime
match its mark is not opened; one that has grown is read from its mark on, new bytes only;
one shortened, edited in place or replaced is read whole, and its rows become exactly its
lines. A line the database will not take is skipped on its own, never holding back the
rest. A session folder or app file you delete is forgotten in the database too. The
history folder itself is different: the database notes which folder it read (its device,
inode and birth time), and if it finds another one there (the folder was lost, moved or
made anew), it keeps every session that did not come with it, however many new ones the
new folder gains, so pnpm db:load can write them back. To forget everything at once,
delete ~/.innernet/db as well.
PGlite runs Postgres single-user, with no autovacuum and no checkpointer, so Innernet vacuums a table and checkpoints after every few hundred rows rewritten or deleted (a re-index counts), and keeps the write-ahead log under 64 MB. The folder stays near 70 MB for an index of 5,000 pages, however often it is rebuilt.
One process opens the folder at a time: a second server works from the files alone and
says so once, and the commands below decline politely while the server has it. A lock
left by a server that crashed is taken over, even if its process id has since gone to
another program; with no Innernet running, deleting owner.lock is always safe.
pnpm db:status # where it lives, its size, what it holds
pnpm db:store # store data/index.json and bring the history up to date with its folders
pnpm db:load # write the stored index and history back out as files (stop the server first)
/activity has a Database card with the same counts and a Store now button, which
runs what db:store runs inside the server that already holds the folder (a same-origin
POST to /api/db/store, on localhost only). db:load adds the history lines that are
missing and never deletes any, and it leaves an index file newer than the stored copy
alone unless you pass --force.
| Variable | Effect |
|---|---|
INNERNET_DB_DIR | Where the database lives (default ~/.innernet/db). |
INNERNET_DB=off | No database: Innernet reads and writes its files alone. |
INNERNET_HISTORY_DIR | Where the history folders live (default ~/.innernet/history). |
The demo's database
The public demo uses Neon serverless Postgres instead, in US East,
through the DATABASE_URL that the Vercel Marketplace sets. Local mode refuses to connect
to it, or to any remote database, whatever is set. It holds two things.
The demo index. On first use, then at most every five minutes, the demo looks for an
index newer than the bundled data/demo/index.json and serves it once it passes the same
leak checks as pnpm index:demo. The server never writes it. To store a fresh one, with
the connection string in the environment (the maintainers keep it in the gitignored
.env.neon.local, which Next.js never loads):
pnpm index:demo
set -a; . ./.env.neon.local; set +a; pnpm db:store --demo
db:store --demo refuses a local index, and anything that names this machine, before a
byte reaches Neon; the store itself refuses the same again, whoever calls it. Without
--demo the commands drop DATABASE_URL from their environment and refuse to run while
INNERNET_DEMO, INNERNET_DEMO_BUILD or VERCEL is set, so a shell left in demo mode
cannot send this machine's index anywhere.
A build that can see DATABASE_URL turns off Turbopack's persistent cache
(next.config.ts): that cache saves the whole environment of the build in .next/cache,
in files anyone on the machine can read, and the connection string belongs only in
.env.neon.local.
The demo's history
The recorder keeps each visitor's events in their browser's localStorage, and when the
demo has a database it also posts them to /api/activity, which keeps them in the same
activity table for 30 days:
- What is stored: per event, the kind (
visit,search,backorforward), the page's path on the demo (one of its own pages, with no query but the search's ownq,tandp: campaign tags and other sites' click ids are dropped in the browser and again on the server), its title or the search words,via(whether the header's buttons or the browser's moved you back or forward), and the server's time. Each event is kept onc
Structure
Folders
Files
- CONTRIBUTING.md
- DESIGN.md
- innernet.config.json
- instrumentation.ts
- next.config.ts
- package.json
- pnpm-lock.yaml
- postcss.config.mjs
- project.json
- proxy.ts
- README.md
- tsconfig.json
Technology
Languages
Frameworks and libraries
Scripts
- index
- index:demo
- db:status
- db:store
- db:load
- dev
- dev:demo
- build
- start
- typecheck
- test:mount
Dependencies
- @electric-sql/pglite
- @neondatabase/serverless
- minisearch
- next
- react
- react-dom
- react-markdown
- remark-gfm
- server-only
- @tailwindcss/postcss
- @types/node
- @types/react
- @types/react-dom
- postcss
- tailwindcss
- tsx
- typescript
See also
- innernetInnernet turns the folders on your machine into a small, connected web.
- quirq_aiThis is the marketing and research site for quirq.
- quitter (apps)All your agent and thread activity in one place.
- instants (apps)A collaboration prototype for teams sharing work, testing ideas, and getting timely feedback.
- docsThis is a Next.js application generated with Create Fumadocs.
- instantsA collaboration prototype for teams sharing work, testing ideas, and getting timely feedback.
- quitterAll your agent and thread activity in one place.
External links
- View innernet on GitHub, at github.com/
quirq-ai/ euler/ apps/ innernet