github-900p-web.mp4
A local viewer for fieldtheory-cli. Runs a Node server on localhost:5787 and opens a browser tab. Reads from ~/.ft-bookmarks/; writes derived caches to ~/.index-n2/.
Third-party tool, not affiliated with fieldtheory-cli.
Made for Macintosh. The GUI runs anywhere; the CLI's default sync targets macOS. For cross-platform options, see the CLI's ft sync --api mode and its caveats.
npm install -g index-n2Requires Node.js and a working fieldtheory-cli install on PATH. No bookmark data needed up front — first sync runs from the sidebar.
index-n2The server binds 127.0.0.1:5787 and opens http://localhost:5787/. If 5787 is taken it walks up to 5797. Stop with Ctrl-C.
Reads ~/.ft-bookmarks/bookmarks.jsonl and bookmarks.db directly. Renders the result as a masonry grid, gallery, lightbox, and duplicates view. Filters across author, type, language, category, color, and full-text search. Wraps ft sync, ft fetch-media, and ft sync --rebuild as sidebar actions with live progress over Server-Sent Events.
~/.ft-bookmarks/ is treated as read-only. The GUI never writes, deletes, or modifies anything inside it. All derived data — thumbnails, color palette cache, dedup index, archive list, settings — lives in ~/.index-n2/ and is rebuildable from scratch.
| Feature | CLI equivalent |
|---|---|
| Full-text search | ft search |
| Filter by author | ft list --author |
| Filter by category | ft list --category |
| Sort by date | ft list --sort |
| Action | Shells out to |
|---|---|
| Sync All | ft sync then ft fetch-media |
| Sync Bookmarks | ft sync |
| Fetch Media | ft fetch-media --limit N |
| Rebuild Bookmarks | ft sync --rebuild --yes |
- Color filter. A dominant palette is extracted per image (median-cut RGB quantization, palette stored in Lab D65 for matching) and cached on disk. A picked hex matches palette entries via population-weighted ΔE with a lightness-aware threshold; a separate mono mode matches images whose chroma falls below ~12.
- Duplicate detection. 64-bit pHash (32×32 → 2D DCT → 8×8 low-frequency block, median threshold) with a 9-bit Hamming radius for images; exact-match grouping over normalized text. Profile pictures and solid-color images are excluded.
- Type filter — text, image, video, gif, link, quoted, thread.
- Sort by engagement — likes, reposts, bookmarks, or random.
- Gallery view. Image-only, flattened across bookmarks.
- Archive. Hide bookmarks from the main grid without touching source data.
Persisted in ~/.index-n2/settings.json. Edit through the gear icon in the sidebar.
| Key | Default | Effect |
|---|---|---|
theme |
system | system / light / dark |
autoplayVideos |
true | Auto-start videos in the lightbox |
autoOpenBrowser |
true | Open a browser tab on startup (restart to apply) |
skipMedia |
false | Sync runs ft sync --no-media (bookmarks only) |
mediaFetchLimit |
0 | Cap for ft fetch-media --limit. 0 disables it. |
skipProfileImages |
false | Adds --skip-profile-images to ft fetch-media |
hideUnfetched |
false | Hide bookmarks with media pending download |
Two additional keys are flipped by "Don't show this again" checkboxes on confirmation dialogs, not the gear icon: cancelMediaWarningSuppressed (cancel fetch-media) and noLimitWarningSuppressed (no-batch-limit warning shown before unbounded Sync / Fetch Media). Reset to Defaults clears all nine.
Unknown keys are stripped on the next save; defaults backfill missing keys. Schema migrations are self-healing.
| Variable | Default | Effect |
|---|---|---|
FT_DATA_DIR |
~/.ft-bookmarks |
Override the ft data directory |
PORT |
5787 | Pin the port. Disables the auto-fallback on collision. |
BROWSER=none |
unset | Skip browser auto-open even if the setting is enabled |
CI |
unset | Skip browser auto-open |
index-n2 --no-open Skip browser auto-open for this run
~/.ft-bookmarks/ fieldtheory-cli, read-only to this app
bookmarks.jsonl raw bookmark records
bookmarks.db SQLite, opened read-only via sql.js-fts5
media-manifest.json URL → local-path map
media/ downloaded media
~/.index-n2/ derived; safe to delete and rebuild
thumbnails/ 1200px JPEGs (only for media > 1200px)
colors.json per-image Lab palettes
dedup.json pHash + text-match cache
archived.json archived bookmark IDs
settings.json user settings
index-n2 process localhost:5787
server.js API + static + media + image pipeline
dist/ Vite build (React 19, TS, Tailwind 4, shadcn/ui)
Per-file caches (palette, pHash, thumbnails) are keyed by media filename and grow incrementally — only files in ~/.ft-bookmarks/media/ that aren't already in the cache trigger work. Bookmark records reload when bookmarks.jsonl or the SQLite mtime changes.
The server is a localhost-only Node process. Defenses:
- Bind to 127.0.0.1.
- Origin check on writes. Every mutating endpoint requires an
Originheader resolving tolocalhostor127.0.0.1. Cross-origin POSTs return 403. - Path traversal + symlink escape guard. Every static-file request —
/media/,/thumbs/, and the SPA bundle indist/— is resolved lexically, thenrealpath-validated to lie under its base directory. - Media extension whitelist. Image and video formats only — no SVG, no HTML.
- Strict CSP. No remote scripts; the only inline script is hash-allowlisted. Styles, images, fonts, and media follow standard SPA carve-outs.
- Subprocess discipline. Spawned
ftprocesses time out at 5 min idle (not wall-clock). Cancel sends SIGINT on Unix.
No remote endpoints are contacted. All media is read from ~/.ft-bookmarks/media/, which ft itself populates.
- Categories are read-only. No in-app classification editor.
- Cancelling
ft fetch-mediamid-run leaves orphaned files on disk: ft writes its manifest at end-of-run, so a cancelled batch's downloads are invisible to ft and re-fetched next time. Bandwidth waste, no corruption. Tracked upstream. - A small set of permanently unfetchable URLs (deleted tweets, protected accounts) keep the "N unfetched" badge non-zero.
git clone https://github.com/refractionweb/index-n2.git
cd index-n2
npm installTwo-process dev loop:
node server.js # API on :5787
npm run dev # Vite on :5173, proxies /api /media /thumbs to :5787Production build:
npm run build # tsc -b && vite build
npm start # serves dist/ from server.jsOther tasks:
npm run typecheck
npm run lint
npm run formatThe codebase is React 19 + TypeScript + Vite + Tailwind 4 + shadcn/ui. State is React Context + useReducer, split per concern under src/contexts/. No Redux, no Zustand. Path imports use @/ for src/. Styling sticks to shadcn semantic tokens; deviations are recorded inline next to the code that needs them.
Built on open source:
- React · Vite · TypeScript — MIT
- Tailwind CSS · shadcn/ui · Radix UI — MIT
- cmdk · open · quantize · clsx · tailwind-merge — MIT
- sharp · class-variance-authority — Apache-2.0
- sql.js-fts5 — MIT
- Remix Icon — Remix Icon License 1.0
- Inter by Rasmus Andersson — OFL-1.1
MIT License · Copyright © 2026 Refraction