zdkmd

zdkmd

The Markdown documentation of your projects, read like a wiki, in the terminal.

zdkmd renders the documents of a git repository or an Obsidian vault and follows their links, with the keyboard or the mouse. One standalone binary for Linux, signed, updated by one command.

Install

curl -fsSL https://zdkmd.zedesk.net/install.sh | sh

Version 1.7.0 · Linux x64 and arm64 · free

Preview

Real screens of zdkmd, in the 16 colors of the terminal theme: it follows yours, light or dark.

What it does

zdkmd reads the documents of a root: the folder of a git repository or of an Obsidian vault. Every key can be changed.

Install

One command installs zdkmd into ~/.local/bin, without root. The script finds the target of the machine, checks the SHA-256 sum of the binary and tries it before replacing the old one.

Versions

What changed in each version.

1.7.0

The distribution: the J4 milestone (plan). The first version installed from https://zdkmd.zedesk.net, which then updates itself.

Added

  • Binaries for four Linux targets, x64 and arm64, glibc and musl, built from one machine by bun run scripts/release.ts: each compressed with its SHA-256 sum, under a manifest signed in Ed25519 whose public key zdkmd carries. The first step of the distribution (plan J4).
  • The installation script, install.sh, to be served by https://zdkmd.zedesk.net: curl -fsSL https://zdkmd.zedesk.net/install.sh | sh (or wget -qO- … | sh) installs the zdkmd of the machine, its target found alone (x64 or arm64, glibc or musl), its SHA-256 sum checked, into ~/.local/bin without root, and gives the line to add when that folder is not in the PATH. Running it again updates or reinstalls; ZDKMD_VERSION installs one of the three latest versions, ZDKMD_INSTALL_DIR another folder, and a folder not writable gets the command to install for all accounts with sudo. On Alpine, it stops before downloading when libstdc++ is missing and gives apk add libstdc++ libgcc. Written in POSIX sh (busybox and dash too).
  • zdkmd --update updates a zdkmd installed by the script: it reads the latest version, checks the Ed25519 signature of its manifest with the public key zdkmd carries, then the size and the SHA-256 sum of the binary of its target, and puts the new binary in place of the old one only once it answers --version; after any failure, the old one is unchanged. It prints zdkmd 1.7.0 → 1.7.1 installed. What's new: https://zdkmd.zedesk.net/en/#1.7.1 (the page of the versions, in the language of zdkmd), or zdkmd 1.7.1 is up to date, and never goes back to an older version. A folder you cannot write gets the command to install there with sudo. A zdkmd built from the sources, by bun run build or by install:local, refuses: it was not installed from zdkmd.zedesk.net.
  • The notice of a new version: once a day at most, after showing its first document, a zdkmd installed by the script reads the latest version and checks the signature of its manifest; when it is newer, the status line says zdkmd 1.7.1 available: zdkmd --update (zdkmd 1.7.1 disponible : zdkmd --update in French), only when no other message is there, gone at the next key. Nothing is installed. Between two checks, the latest version known is kept in $XDG_STATE_HOME/zdkmd/update.json (~/.local/state/zdkmd/update.json), and told again at each launch; offline, nothing is said. check = false under [updates] in the configuration file turns it off: the template documents it, --check-config checks it. A zdkmd built from the sources never checks.
  • The site of zdkmd, https://zdkmd.zedesk.net, in English and in French: the installation command with its Copy button, real screens of zdkmd taken in a terminal (a rendered document, Mermaid, the Picker, the search in the folder, the follow mode), what it does, the installation (Alpine, a given version, another folder, for all accounts, update, uninstall), the versions of this changelog, each under its anchor (/en/#1.7.0, the "What's new" link of --update), and what the server records. Static, readable without JavaScript, without cookie nor outside resource. Served by nginx in a container, which also serves install.sh and the releases, the .gz without Content-Encoding, and writes one JSON line per download with the IP address cut short (/24 in IPv4, /48 in IPv6).
  • A licence: zdkmd is freeware, by zeDesk (LICENSE, in English and in French). It is free to use, and to copy and redistribute unmodified, free of charge, alone or in a package; you may study, modify and reverse engineer it for your own use, but not redistribute a modified copy. The rights of the open source components it includes prevail: JavaScriptCore and TinyCC, libraries of the Bun runtime, are under the GNU Lesser General Public License, and the object form of zdkmd needed to link it again with a modified version of them is offered for three years from each release.
  • zdkmd --licenses prints the licence of zdkmd, then the notices of its third-party components: each open source component of the binary (the packages, the native library of OpenTUI and what it holds, the grammars, the Bun runtime and the libraries it links), its version and copyright lines, then each licence text once, after a header that gives the sources of the two libraries under the LGPL and how to relink. The same text is at https://zdkmd.zedesk.net/third-party-notices.txt, and the licence page of the site has a "Third-party components" section.

Fixed

  • The text below the scrollbar no longer shows dimmed: the dimmed thumb ▐, in the last column of the screen, dimmed the start of the next row of the document in every terminal, a defect of OpenTUI 0.5.14. The thumb becomes a thin ▕, in the default color without attribute.

Changed

  • Ctrl-Q quits instead of q: an accidental press on q quit zdkmd. Ctrl-C still quits, and q alone now does nothing in the document. The hint of the status line becomes Ctrl-Q quit (Ctrl-Q quitter in French), still the last one and the first dropped when the space runs out; the help and the empty state ("Press Ctrl-Q to quit.") show Ctrl-Q too. To quit with q again, write in the configuration file, under [keys]: quit = ["q", "<C-q>", "<C-c>"].

1.6.0

Changed

  • The help no longer cuts a line too long for its window with …: the line goes on in the rows below, under the column of the descriptions, cut at its spaces (13 lines of 76 in French at 80 columns, 10 in English). The selected line is in reverse video on all its rows, a click on any of them chooses it, and the selected line is always shown whole; a new size of the terminal splits the lines again. In a narrow window, where the column of the descriptions would leave fewer than 16 columns, the rows below start at the left. The other floating windows keep one row per item.

1.5.0

Follow mode and backlinks: the J3 milestone (plan).

Added

  • Follow mode: F, or --follow at launch, opens on its own each Markdown document written in the root, about a second after it is written, to read next to an editor or Claude Code. The root is scanned again every second, not watched; the documents followed are those the Picker lists (no ignored files, no hidden folders), and a deletion opens nothing. A document opens as a followed link (Ctrl-O comes back, a document already open at its place); it waits while a window is open, a search is typed, the Selection mode is on or the mouse button is down, and a document you open meanwhile wins. In the empty state, the first document written opens and closes the Picker. Without a file, --follow opens the latest document written instead of the README. The status line shows follow while it is on. Action name toggle_follow.
  • O lists the backlinks of the document: every link of the other documents of the root that leads to it, one row per link, path:line then the line with the link highlighted, and their count (5 links · 3 documents). Markdown links count with or without an anchor, as do wikilinks in all their forms, embeds of the note, an ambiguous wikilink naming it, and a link to a folder that opens it (its README.md, else its index.md). Typing filters the rows on their path and line; Enter opens the document at the line of the link, the link selected (a folded callout holding it unfolds), and Ctrl-O comes back. Action name open_backlinks.
  • A document opens at a heading from the command line: zdkmd 'docs/design.md#cache', the heading written as a link writes it (GitHub slug, exact text or {#id}, %20 decoded); docs/#notes opens the README of the folder at it. The launch makes no history entry, so Ctrl-O has nothing to go back to. A heading not found opens the document at the top with a warning, shown instead of the one of a faulty configuration file. A file whose name holds # still opens as it is; a missing file is named without its anchor. Quote the argument: # is a pattern character of zsh.
  • Dragging the mouse to an edge of the view scrolls the document, the selection following: slowly on the second row or the last row of the view, fast on the first row or the status line. It stops when the pointer goes back into the text, at the release, at a bound of the document, or when a window opens. During a drag, the wheel now moves the selection too.
  • Seven more languages highlighted in code blocks: TSX, JSX, CSS, HTML (with the CSS of its <style> and the JavaScript of its <script>), TOML, Dockerfile and JSON Lines, under their usual names, case ignored (htm, xhtml, docker, containerfile, ndjson). Tag names are colored as keywords, attributes and TOML keys as properties, TOML sections as types; a CSS unit belongs to its number (12px), and so does the 0x of a Go number. SCSS, Less and PostCSS stay plain.

1.4.0

Changed

  • The search centers its match: when n or N has to scroll to the next match, the match shows in the middle of the view, with the text around it, instead of on its last row (against the status line) or its first one; Enter does the same when typing the query has scrolled the view. Near the start or the end of the document, the view goes as far as it scrolls. Typing the query still scrolls just enough, as do the links, the code blocks and the selection cursor.

1.3.0

Added

  • Pasting in the search and in the floating windows: a text pasted in the terminal (Ctrl-Shift-V, Shift-Insert, the middle button) is added to the query being typed, after / or in the Picker, the buffers, the table of contents, the file tree, the links, the information of the document, the search in the folder and the help. The query stays on one line: line breaks and tabs become spaces, other control characters are dropped, 256 characters at most. In the document, a paste does nothing.

1.2.0

Added

  • Markdown links written from the root of the project, as Docsify writes them (docs/features/index.md): a link or an image missing next to its document is looked for from the root, unless it starts with ./ or ../. The document still comes first, so only links that were broken change target.

1.1.0

Added

  • A scrollbar: a dimmed thumb ▐ in the right margin of a document that scrolls shows where the view is in it.

Changed

  • At the end of a document taller than the view, the last line has three blank rows below it (a third of the view at most) instead of sitting on the status line, also when a link, a search match or the selection cursor takes the view there.

1.0.0

The complete V1: the J2 milestone (plan).

Added

  • e opens the document shown in your editor at the line you read, and shows it again, reloaded, when you quit the editor. The editor is the editor setting of the configuration file (editor = "helix {file}:{line}"), else $VISUAL, else $EDITOR; it is run without a shell.
  • Callouts: GitHub alerts (> [!NOTE]…) and Obsidian callouts (> [!info] Title, aliases included), with a colored bar, an icon and a title. A click on its title folds or unfolds a foldable callout; z unfolds them all, and folds them back.
  • Mermaid diagrams drawn in characters: flowchart, sequence, class, state, entity-relationship, pie, git graph, mind map and timeline. Other types (gantt) and unreadable sources show the source with the reason. A diagram wider than the window scrolls sideways in place: h/l, ←/→, Shift + wheel or the wheel sideways.
  • The file tree (-): the folders and documents of the root, positioned on the document shown.
  • The search in every document of the root (Ctrl-F), as you type, limited by folder/, !folder/ leaving one out, "quoted text" taken as is; Enter opens the document at the line. In the file tree, Ctrl-F searches the folder of the selected item.
  • The links of the document (o): every outgoing link, broken and ambiguous ones marked; Enter follows one.
  • The information of the document (i): path, root, modification date, size, counts of lines, words, headings, links, inserts and broken links, then the frontmatter; Enter copies a value.
  • Images and embedded files shown as inserts (▣ Alt text · assets/schema.png) that open in the system viewer; a missing one shows as a broken link. A wikilink or an embed finds an attachment by its file name anywhere in the root, as Obsidian does.
  • --help gives an example of the editor setting, and the configuration template documents it.

Changed

  • A link to a folder without README.md nor index.md opens the file tree on that folder, instead of the Picker filtered on its path.
  • Ctrl-F is now bound: a configuration file that gives <C-f> to another action reports a conflict.
  • The status line shows e edit among its key hints; in French, the table of contents is hinted t sommaire.
  • The message of a file opened by the system writes its path from the root, else from ~/, instead of its absolute path.

Fixed

  • A key handled while a frame was drawn could be shown only at the next event (the count of the selection mode lagged behind, for instance): OpenTUI 0.5.14 is patched so that a render asked for as a frame ends is no longer lost.
  • The help lists every key of the V1 and says that a click opens an insert too.

0.1.0

The core of navigation: the J1 milestone (plan), enough to replace leaf day to day.

Added

  • Markdown rendered in the terminal: headings, lists, task lists, GFM tables, quotes, code blocks highlighted in ten languages, footnotes, frontmatter. The 16 colors of the terminal theme only.
  • A root found on its own (the .git or .obsidian folder), its documents listed without what git ignores.
  • Links followed with Tab and Enter or a click: relative Markdown links, anchors, Obsidian wikilinks (a choice when ambiguous); the target shown in the status line, broken links in red; web links, mailto and other files handed to xdg-open.
  • The Picker (f): fuzzy matching on the paths, the way fzf does; buffers (b, J, K, x), a navigation history (Ctrl-O, L), the table of contents (t), the search in the document (/), the help (?).
  • The document shown reloaded when its file changes, at the same section.
  • Copy: a code block (c, then y), a passage by dragging the mouse or in the selection mode (v), the text stitched back together; the system clipboard, else OSC 52.
  • An English or French interface; keys and language set in an optional configuration file (--create-config, --check-config, --default-config); --lang, --debug-log, --version, --help.
  • A standalone binary (bun run build, bun run install:local) and a benchmark of the PRD metrics (bun run bench).

Privacy

zdkmd sends no telemetry. The server keeps only what it takes to count installations and updates.