Show HN: Ledge.sh – Runnable Markdown Notes

Hacker News Top Tools

Summary

Ledge.sh is a free, open-source (Apache-2.0) Markdown notebook for developers and DevOps that lets you run shell, code, SQL, and AI prompt blocks directly inside your notes, with persistent per-note shells and optional remote-server execution over SSH from desktop or mobile.

Hi HN,<p>Ledge is a Markdown notebook that runs shell commands, code, SQL, etc from inside your own notes.<p>I built Ledge because I spend much of my day copy&#x2F;pasting commands from my notes into the terminal. I was inspired by how much cmux helped me organize my terminals - but there was still a split brain between my notes and frequently run commands. I&#x27;ve been daily-driving it for the past few weeks and use it for deploys, API calls, smoke tests, etc.<p>Ledge runs your real shell just like a terminal app and can be hosted locally or remotely over SSH using ledge-server. I&#x27;ve been building it since July and have recently added support for all the major platforms: Mac (Silicon), Windows (WSL required), Linux, iOS, Android. Mobile devices require SSH access to a ledge-server and Android is still in beta and looking for beta testers (see link on website)!<p>It&#x27;s built on Bun and Electrobun and is free and open-source. Feel free to review the code and contribute at <a href="https:&#x2F;&#x2F;github.com&#x2F;ledgesh&#x2F;ledge" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;ledgesh&#x2F;ledge</a><p>Feedback is very much welcome. Any must-have features that are missing?
Original Article
View Cached Full Text

Cached at: 09/30/26, 05:03 PM

# Ledge: The notebook that runs code Source: [https://ledge.sh/](https://ledge.sh/) ## The notebook that runs shell commands, code, SQL, and AI prompts New subscriptions this week Stripe API notes New subscriptions this week \-\-\- profile: stripe \-\-\- Monday check on last week's signups\. The stripe profile holds the API key and database URL\. Endpoints are inStripe 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 Server`curl \-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\) ## Each note has its own shell 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\. - ⌘↩runs the block under the caret\. Output streams in beneath it\. - ⇧⌘↩sends the block to the note's terminal drawer, a second shell you can keep typing in\. - A running block takes input\. Answer a`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\. [Docs: Running Code](https://ledge.sh/docs/running-code) 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 ``` ## Keep your notes on a server 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\. - No account, no service, and no port to open\. The only thing between you and the server is ssh\. - Shells and running blocks belong to the server\. Close the laptop and they keep going\. The output is waiting when you open it again\. - Your computer, your phone, and a second window can all be on one server at once, each with its own tabs\. - Sign in with an ssh key, or with a password kept in the system keychain\. Ledge pins the server's host key and refuses any other\. - Install with the command below\.`ledge backup`puts an encrypted copy of the notes in an S3\-compatible bucket every hour\. Install Ledge Server`curl \-fsSL https://ledge\.sh/server\.sh \| sh` [Docs: Keep Notes on a Remote Server](https://ledge.sh/docs/keep-notes-on-a-remote-server) \-\-\- cwd: ~/api \-\-\- Cut 2\.4\.2 from the server, not the laptop\. The build takes a minute\. ## The same notes on your phone 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\. - Read, edit, and create notes with live preview\. Search, tags, backlinks, daily notes, and wikilinks all work\. - Tap Run on a block and it runs on the server\. Switch apps and it keeps running\. The output is waiting when you come back\. - `^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\. - Scan the pairing code that`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\. - Locked notes unlock with the passphrase\. The phone stores none of it\. [Docs: Ledge on Your Phone](https://ledge.sh/docs/ledge-on-your-phone) Media server Two episodes transcoding\. Check before starting a third\. BI\[\[ ## Run blocks on other machines 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\. - Declare several hosts and Ledge asks which one on every run\. Enter repeats your last pick\. - Mark a block`confirm`and Ledge shows the code, names the machine, and asks before running it\. - Authentication is your own`ssh`\. Keys, agents,`~/\.ssh/config`, and 2FA work as they do in a terminal\. - The note's`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\. [Docs: Run Code on Remote Hosts](https://ledge.sh/docs/run-code-on-remote-hosts) \-\-\- cwd: ~/Projects/api host: staging, deploy@prod \-\-\- Stale session keys after the 2\.4\.1 deploy\. Flush staging, verify, then prod\. sh confirm="Flush the session cache?" ## Put notes side by side ⌘Dsplits the view left and right,⇧⌘Dtop and bottom, and either side can split again\. Each pane has its own tabs\. - Every note keeps its own shell, so two panes are two working environments\. A run keeps streaming in the pane you left\. - Drag a tab to another pane\. It keeps its caret, scroll position, undo history, and any output still streaming\. - ⌃Tabcycles tabs\.⌃1to⌃9jump to one by position\. - Each workspace saves its layout and restores it at the next launch\.⌘1to⌘9switch workspaces, layout and all\. - One terminal drawer spans the window and follows the focused note\. [Docs: Panes and Tabs](https://ledge.sh/docs/panes-and-tabs) cwd: ~/Projects/api Trial length bug Some trials end a day early\. ## Secrets stay out of your notes 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\. - Notes get synced, backed up, and read by agents\. A profile value stays on the machine\. - Any number of notes can share one profile\. An`env:`line overrides a value for one note\. - A built\-in editor shows the values masked, with one toggle to reveal them\. - `envFile: \.env`loads the project's own dotenv for everything that is not a secret\. [Docs: Profiles and Secrets](https://ledge.sh/docs/profiles-and-secrets) \-\-\- cwd: ~/Projects/api profile:deploy \-\-\- Backup restore drill\. The AWS keys come from the deploy profile\. sh ``` aws s3 ls s3://ledge-api-backups/nightly/ | tail -2 ``` ## Built to be worked by agents 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\. - Notes are addressed by title, which survives renames, so an agent's references do not go stale\. - A terminal opened inside a note knows which note it is in\. "Add a TODO section here" needs no explanation\. - A`prompt`code block is a runnable block\.⌘↩pipes it to the agent and the reply streams in beneath it\. - Agents can read your settings and the manual, so "check the Ledge docs" is a fair instruction\. - There is no delete tool, and locked notes refuse their bodies to every agent\. [Docs: Agents and Ledge](https://ledge.sh/docs/agents-and-ledge) Release 2\.5\.0 Tag after the migration bake\. Checklist inRelease 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 ``` ## Lock the notes that matter 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\. - The title, frontmatter, and tags stay visible, so links and navigation keep working\. - Search and backlinks skip locked bodies and say how many notes they skipped\. - One passphrase covers every locked note\.⌘Lrelocks, and the vault relocks itself after 15 minutes idle\. - Images pasted into a locked note are sealed with it\. - A locked note is self\-contained\. On another machine it unlocks with the passphrase alone\. There is no recovery\. [Docs: Note Locking](https://ledge.sh/docs/note-locking) 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" ## Sync with anything, share with git 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\. - Ledge watches the folder and follows outside changes live, even in an open note\. - Push a workspace to a git remote and the people you work with clone it\. Each clone attaches as an ordinary workspace, and a pull shows up in the sidebar with nothing to refresh\. - One note changed in two clones is a git conflict like any other\. If a pull rewrites a note you have open, your version keeps the file and the other goes to the workspace trash\. - A Sync note in the workspace pulls, commits, and pushes with one⌘↩\. - Profiles never enter the folder, locked notes travel as ciphertext, and the trash stays out of git\. [Docs: Keep Notes Synced](https://ledge.sh/docs/tutorial-keep-notes-synced) 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 ``` ## Ways to use Ledge 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]) ``` [Docs:Running Code](https://ledge.sh/docs/running-code#add-a-language) 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 ``` [Docs:Run a Project from a Note](https://ledge.sh/docs/tutorial-run-a-project) Incident response redis ``` INFO keyspace ``` Done12 ms ``` # Keyspace db0:keys=4812339,expires=0,avg_ttl=0 ``` [Docs:Profiles and Secrets](https://ledge.sh/docs/profiles-and-secrets) Learning to code ruby ``` %w[a b c].each_with_index do |s, i| puts "#{i}: #{s}" end ``` [Docs:Running Code](https://ledge.sh/docs/running-code#interpreted-languages) Homelab sh ``` zpool list -o name,cap,health ``` Done1\.1 shomelab ``` NAME CAP HEALTH tank 81% ONLINE ``` [Docs:Run Code on Remote Hosts](https://ledge.sh/docs/run-code-on-remote-hosts) Daily journal prompt ``` List the open tasks in [[2026-07-30]] ``` Done4\.2 s ``` - Send the lease renewal back - Finish the chapter 4 notes ``` [Docs:Daily Notes and Templates](https://ledge.sh/docs/daily-notes-and-templates) ## The notes app underneath Everything else you expect from a notes app is here\. ### Your languages 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" ### Daily notes and templates ⌘Jopens 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\}\}\]\] ### A workspace is a folder Attach a project folder and its Markdown files become notes where they are, running their blocks in the project\.⌘1to⌘9switch workspaces, and each keeps its own pane layout\. Removing an attached folder from Ledge touches no files\. Attach Folder as Workspace… ### Live preview 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 ### Paste anything Copy part of a web page, a Slack thread, or a Google Doc, and⌘Vpastes it as Markdown: headings, lists, checkboxes, links, and tables included\.⇧⌘Vpastes 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\. \!\[\]\(\.ledge\-assets/pasted\-2026\-07\-31\.png\) ### Find your way back ⌘Popens 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\]\] ### Tags and favorites Tag a note with inline`\#hashtags`or a frontmatter`tags:`line, and⌥⌘Tlists every tag with its count\. Pressfon 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 ### Deleting is undoable ⌫moves a note to the workspace trash and shows an Undo strip\. The Trash section in the sidebar holds it for 30 days, whererrestores it and permanent deletion asks first\. "trash\.ttlDays": 30 ### The ledge CLI 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 Server`curl \-fsSL https://ledge\.sh/server\.sh \| sh` FreeApache\-2\.0No accountPlain filesMac, Linux, Windows, iPhone

Similar Articles