[ STATUS: SHIPPED ] [ VERSION: v1.5.0 ] [ PLATFORM: LINUX ] [ CLEARANCE: NONE REQUIRED ]

Your notes never leave this machine.

A Markdown note taker that lives in your terminal — and in your browser, from the same 19 MB binary. One SQLite file on your own disk. Nothing running when you quit.

$ curl -L .../releases/latest/download/nib-linux-amd64 -o nib
nib

    
[ 01 ]
Every key, no menus

The note you are reading fills the screen. Down the right-hand side sits a small box of facts about it, and under that, the list of everything else. Press ? and every key is listed without leaving the app.

Move

w s between notes, j k to scroll the one you are reading. Neither ever changes meaning.

Write

n for a new note, to edit. Leave the title blank and the first line becomes it.

Format

Bold, italic, strikethrough, code, headings, three kinds of list, quotes and rules — all on keys.

Search

/ narrows titles and bodies as you type. Under five milliseconds across a thousand notes.

Recover

d moves a note to a trash. u walks back, T browses thirty days of them.

Aim

Click a pane and and the wheel act on it — scroll the note, or step through the list. The live pane is the bright-bordered one.

Copy

R shows the raw Markdown with no borders, and hands the mouse back, so a selection is the text and nothing else.

Links

The preview shows the link text, not the URL. Press o to open one — or pick from a list when the note holds several.

Your editor

e opens the note straight in $EDITOR; ctrl+e does it mid-edit.

Browser

W opens the same notes in a React UI, served from inside the binary, bound to localhost.

[ 02 ]
Point at it, then scroll

Click a pane to aim at it. Click the note and and the wheel scroll it; click the list and they step between notes instead. Whichever you aimed at wears the bright border, so there is always something on screen saying where the next scroll will land.

w s and j k ignore all of it. They always mean “another note” and “scroll this one” — so there is always a key whose effect does not depend on where the mouse has been.

The honest cost: for the app to know which pane you clicked, the terminal has to hand it the mouse, so selecting text on this screen means holding shift while you drag. It is the only screen that holds it — the editor, the trash, the help and R all give the mouse straight back.

Aimed at the note — the wheel scrolls it.

[ 03 ]
Lists finish themselves

Press at the end of a list item and the next one is already waiting — tasks, bullets, numbers that keep counting, blockquotes. Indentation is kept, so a nested item stays nested.

Press it once more on the empty item and the marker disappears. That is how a list is finished: no mouse, no menu.

alt+↵ gives a plain line break. Shift+Enter cannot: a terminal sends the identical byte for it as for Enter.

[ 04 ]
Drop a file into the note

In the browser, drag a picture onto the editor — or paste one, so a screenshot goes from PrtSc to Ctrl+V without ever becoming a file you have to name and then find again.

In the terminal, drag the file onto the window. A terminal has no drag-and-drop of its own: it pastes the file's path. nib notices a paste that names only files that exist and attaches them instead. Anything else pastes as the text it is.

Every file is named after a hash of its own contents, so the same picture in three notes is stored once. Remove the line and the file waits thirty days in a trash before it goes — the same grace a deleted note gets.

The preview names a picture rather than drawing it. Terminals that can show images need the kitty, iTerm2 or sixel protocol, and most have none of them — so o opens the real file in your own viewer. One thing in the note opens straight away; several bring up a list to choose from.

The name you see is a real terminal hyperlink, so ctrl-click opens the file too — though o is the route that works everywhere.

nib
# drag holiday.png onto the window

![holiday.png](attachments/8f3a91c2d4e5f607.png)

~/.local/share/nib/
  nib.db
  attachments/8f3a91c2d4e5f607.png
[ 05 ]
Nothing to boot first

Measured in one sitting on one machine, every row the same way — wall clock around a subprocess, median of twenty-five runs. The first four are what each runtime costs before a line of your code runs.

/bin/true — process floor0.8 ms
Python, empty script14.9 ms
Node, empty script24.3 ms
Node, after loading React49.7 ms
nib — opened 1,000 notes, ready to serve18.7 ms

The last row is not a floor. It is the whole job — opening the database, reading every note, standing up the server — and it finishes before Node has started up with nothing in it at all.

Go is not magic; a careful Rust or C build would beat it. What Go buys is that the fast path is the default one.

[ 06 ]
What it costs your machine

Measured on an Intel i5-13450HX running Ubuntu, with a thousand notes in the database.

Ready with 1,000 notes
0ms
Median of 25 runs, 18–20 ms across them.
Binary
0MB
Static and stripped. No runtime, no system libraries.
Memory, terminal UI
0MB
The smallest Chrome renderer on the same machine was 155 MB.
After you quit
none
No daemon, no background process, nothing left listening.
Search across 1,000
0ms
Finishes inside a single frame at 60 Hz.
Render a note
0µs
What moving the cursor actually costs.
Disk, 1,000 notes
0KB
247 KB of that is the note text itself.
CPU, open and idle
0%
Of one core. It is the render loop, and it is not zero.

These are real measurements, not estimates — but they are one machine on one afternoon, with everything in the page cache. Run them yourself and the absolute numbers will differ. The ratios are the part that travels.

[ 07 ]
Chain of custody

Every note lives in one SQLite file on your own disk, with any files you attached in a folder beside it. Copy that folder and you have copied everything — there is no export, because there is nothing to export it from.

Nothing is destroyed in a hurry either. A deleted note sits in a trash for thirty days, an attachment nothing refers to gets the same thirty, and the database is snapshotted every time the app starts, keeping the last ten.

The browser interface binds to 127.0.0.1, so nothing else on your network can reach it. No telemetry, no sync, no server, no account to create.

[ SNAPSHOT ON EVERY LAUNCH ] [ 30-DAY TRASH ]
~/.local/share/nib/
  nib.db — every note
  backups/ — the last 10

$ cp nib.db /somewhere/safe
done.
[ 08 ]
Same notes, in a browser

Press W in the terminal and the same notes open in a browser — a formatting toolbar, live preview and a light or dark theme, for the times you would rather point at something than remember a key.

Double-click the text and you are typing. A note opens rendered, because rendered is how a note is worth reading — double-click any part of it and the pane becomes its Markdown, cursor already on the line you pointed at. It saves as you go, and ctrl+s and esc still mean what they mean in the terminal.

It is the same file, open twice. Write in one, press r in the other and it is there. The page is served from inside the binary, so there is nothing to install and nothing to deploy.

Files open instead of downloading. A clip plays in the paragraph you dropped it into, with its name underneath. A .docx is converted and read in a tab of its own, a .csv is drawn as a table, and a picture or a video takes a corner you can drag to resize. All of it happens in the page — nothing is uploaded anywhere to make it work.

Bound to 127.0.0.1, so it is reachable from this machine and nowhere else. Press W again and the server stops.

[ LOCALHOST ONLY ] [ DOUBLE-CLICK TO EDIT ] [ MEDIA PLAYS IN THE NOTE ] [ REACT + TAILWIND ] [ EMBEDDED IN THE BINARY ]
localhost:4321
◆ Notes
+ New note
⌕ Search notes…
Meeting notesjust now
Shipping checklist2 hours ago
Reading listyesterday
Ideas3 days ago
Meeting notes
WritePreview
HB IS <> { }🔗 1.
Meeting notes — 14 Sep
[✓]Agree the release date
[✓]Write the migration plan
[ ]Draft the rollback steps
[ ]Share with the team
Ship Thursday if CI is green.
Context is in the wiki.
[ 09 ]
Your notes, scriptable

Whenever the web UI is running there is a small HTTP API beside it, on the same notes. Enough to write one from a shell script, wire up a hotkey, or pull everything out into a file.

GET/api/notesList every note, newest first
GET/api/notes?q=termSearch titles and bodies
GET/api/notes/:idFetch one note
POST/api/notesCreate — the title may be omitted, it comes from the first line
PUT/api/notes/:idUpdate, same shape
DELETE/api/notes/:idMove to the trash
GET/api/trashList what is still recoverable
POST/api/trash/:idRestore a note
$ nib --web & $ curl localhost:4321/api/notes $ curl -X POST localhost:4321/api/notes \ -H 'Content-Type: application/json' \ -d '{"content": "# From the shell"}'

The trash routes only act on trashed notes — a live note returns 404, so nothing can be destroyed without being trashed first.