Data exploration
sql
select plan, count(*) from signups group by plan;
Done41 ms
plan | count ------+------- free | 1204 pro | 187
python
import pandas as pd
df = pd.read_csv("signups.csv")
print(df["source"].mode()[0])New subscriptions this week
Stripe API notes
New subscriptions this week
Monday check on last week's signups. The stripe profile holds the API key and database URL. Endpoints are in Stripe API notes. #growth
sh
curl -sG https://api.stripe.com/v1/subscriptions -u "$STRIPE_KEY:" \ -d limit=100 -d "created[gte]=$(date -v-7d +%s)" > subs.json jq -r '.data[].plan.nickname' subs.json | sort | uniq -c | sort -rn
24 Pro, 11 Team, and 3 Business since last Monday.
python
import json
subs = json.load(open("subs.json"))["data"]
mrr = sum(s["plan"]["amount"] for s in subs) / 100
print(f"{len(subs)} subscriptions, +${mrr:,.0f} MRR")38 new subscriptions, adding $2,682 in MRR.
sql
select source, count(*) as upgrades from accounts where upgraded_at >= now() - interval '7 days' group by source order by upgrades desc;
The database agrees: 38 upgrades. Newsletter 17, docs 12, referrals 9.
prompt
Write this week's growth update from this note for #team
Ledge is a Markdown notebook for developers and DevOps. Write a note, press ⌘↩ on a code block, and its output streams in beneath it. In your shell, in your project, on your machine, or on a server.
Install Ledge Servercurl -fsSL https://ledge.sh/server.sh | sh
Free and open source · Apache-2.0 · macOS on Apple Silicon · Linux · Windows 11 with WSL · iPhone and iPad · Android (beta)
Shell blocks run in a persistent shell that belongs to the note. A cd, an exported variable, or an activated virtualenv carries into the next run.
sudo password or a [y/N] in the output panel.cwd: and env: in the frontmatter set where the note's shells start and what they carry.norun on a fence takes the Run button away. Use it for a command you are quoting, not running.Invoice rounding bug
Totals drift by a cent on partial refunds. Repro first. #billing
sh
cd services/billing && source .venv/bin/activate
sh
pytest -q --tb=no tests/test_refunds.py
Point Ledge at a machine over ssh and the notes live there. The server holds the files and runs the shells. The app on your computer is the window onto it.
ledge backup puts an encrypted copy of the notes in an S3-compatible bucket every hour.Install Ledge Servercurl -fsSL https://ledge.sh/server.sh | sh
Cut 2.4.2 from the server, not the laptop. The build takes a minute.
Ledge runs on iPhone and iPad as a window onto the same server. The phone holds no notes. It reads, edits, and runs what is on the server.
^C, ^D, Escape, and the arrow keys sit above the keyboard, so you can interrupt a command, quit a pager, or move around in htop.ledge pair prints on the server. The phone signs in with a key made in the Secure Enclave that never leaves it, and that key can speak to Ledge and nothing else.Media server
Two episodes transcoding. Check before starting a third.
BI[[
Add a host: line to the frontmatter and every run in the note happens over ssh on that host. The note itself stays where it is.
confirm and Ledge shows the code, names the machine, and asks before running it.ssh. Keys, agents, ~/.ssh/config, and 2FA work as they do in a terminal.cwd and env travel with the run. Profiles and secrets never do.python, node, and other interpreted blocks run on the host too, with a per-machine interpreter override in Settings.Stale session keys after the 2.4.1 deploy. Flush staging, verify, then prod.
sh confirm="Flush the session cache?"
⌘D splits the view left and right, ⇧⌘D top and bottom, and either side can split again. Each pane has its own tabs.
cwd: ~/Projects/api
Trial length bug
Some trials end a day early.
A profile is a named file of environment variables kept outside your notes folder. A note that names it gets the variables in its shells. The note carries only the name.
profile: deploy in the frontmatter reads ~/.config/ledge/profiles/deploy.env, a plain dotenv file readable only by you.env: line overrides a value for one note.envFile: .env loads the project's own dotenv for everything that is not a secret.Backup restore drill. The AWS keys come from the deploy profile.
sh
aws s3 ls s3://ledge-api-backups/nightly/ | tail -2
Ledge ships an MCP server. claude mcp add ledge -- ledge mcp connects Claude Code, and any MCP agent can read, search, create, and edit your notes through it.
prompt code block is a runnable block. ⌘↩ pipes it to the agent and the reply streams in beneath it.Release 2.5.0
Tag after the migration bake. Checklist in Release runbook. #release
sh
git log --oneline v2.4.1..HEAD | head -3
Done84 ms
9f31c22 storage: chunked uploads for assets over 50 MB 41b0a07 api: rate limit per token, not per IP c2ade19 fix: retry idempotent writes on 5xx
Locking encrypts a note's body on disk behind a passphrase. Agents never see it, sync services carry ciphertext, and reading it requires an unlock.
This note is locked. Its body stays sealed on disk, away from agents and search, until you unlock.
Unlock Notes…
dev@mbp notes % ledge cat "Comp review 2026"
Notes are plain files in ordinary folders, so syncing them is syncing a folder: iCloud Drive, Dropbox, git, Syncthing, or whatever you already use. There is no database on the side.
Sync
This workspace is a repo. One run commits and pushes every note in it.
sh
git add -A && git commit -m "notes $(date +%F)" || true git pull --rebase && git push
Six kinds of notes, each with the manual page that covers it.
Data exploration
sql
select plan, count(*) from signups group by plan;
Done41 ms
plan | count ------+------- free | 1204 pro | 187
python
import pandas as pd
df = pd.read_csv("signups.csv")
print(df["source"].mode()[0])Project runbook
cwd: ~/Projects/api
envFile: .env
sh
docker compose up -d db make migrate
Done3.8 s
✔ Container api-db-1 Started migrated to 0043_signup_source
Incident response
redis
INFO keyspace
Done12 ms
# Keyspace db0:keys=4812339,expires=0,avg_ttl=0
Learning to code
ruby
%w[a b c].each_with_index do |s, i|
puts "#{i}: #{s}"
endHomelab
sh
zpool list -o name,cap,health
Done1.1 shomelab
NAME CAP HEALTH tank 81% ONLINE
Daily journal
prompt
List the open tasks in [[2026-07-30]]
Done4.2 s
- Send the lease renewal back - Finish the chapter 4 notes
Everything else you expect from a notes app is here.
Python, Node, Ruby, and PHP run out of the box. A redis block feeds redis-cli, pointed at staging or prod by the note's REDIS_URL. TypeScript uses the Bun runtime bundled with the app, so ts blocks run with nothing installed. Adding an interpreter is one line in Settings.
"sql": "psql \"$DATABASE_URL\" -f"
⌘J opens today's note, stamped from your daily template. Templates are ordinary notes with {{date}} and {{yesterday}} tokens, and one carrying a prompt block gives every morning a one-keystroke briefing.
promptSummarize [[{{yesterday}}]]
Attach a project folder and its Markdown files become notes where they are, running their blocks in the project. ⌘1 to ⌘9 switch workspaces, and each keeps its own pane layout. Removing an attached folder from Ledge touches no files.
Attach Folder as Workspace…
Syntax hides away from the caret: **bold** shows bold, links show their label, tables and images render in place, and a checkbox is clickable. Move the caret in and the exact characters come back. One setting shows every character all the time.
"editor.livePreview": false
Copy part of a web page, a Slack thread, or a Google Doc, and ⌘V pastes it as Markdown: headings, lists, checkboxes, links, and tables included. ⇧⌘V pastes the plain text. An image on the pasteboard lands in the workspace as a file, rendering in place; pasted into a locked note, it is encrypted from the first byte.

⌘P opens a note by title; type # for full-text search. Wikilinks address titles, so a retitle never leaves a stale link. Backlinks and outline each get a panel.
[[Oncall handbook#Paging]]
Tag a note with inline #hashtags or a frontmatter tags: line, and ⌥⌘T lists every tag with its count. Press f on a note's row, or click its star, and it sits in a Favorites section at the top of the sidebar, however deep in a folder it lives. Both are frontmatter, so they travel with the file.
favorite: true
⌫ moves a note to the workspace trash and shows an Undo strip. The Trash section in the sidebar holds it for 30 days, where r restores it and permanent deletion asks first.
"trash.ttlDays": 30
Lists, reads, searches, creates, and appends from any terminal, and the app follows along live. Results go to stdout, everything conversational to stderr, so pipes stay clean, and --json switches any verb to machine-readable output.
git log --oneline -5 | ledge append "Release notes"
Free and open source under Apache-2.0. No account required, and no database on the side. Your notes are Markdown files on your computer or on a server you run, and they stay yours.
Install Ledge Servercurl -fsSL https://ledge.sh/server.sh | sh
FreeApache-2.0No accountPlain filesMac, Linux, Windows, iPhone