Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Astrolabe CLI

Astrolabe CLI is a reader, a small Vim-style editor and a set of shell verbs for a folder of Markdown notes. It is the terminal companion to Astrolabe, the notes app for the web, desktop and phone, and it opens the same vault; it also works on its own, on any folder of .md files or on an Obsidian vault exactly as it is. The command is astrolabe, and the installer adds the short alias ast.

Two kitty windows on the same vault. The Arabic note is shaped and laid out right to left by Astrolabe CLI; kitty does no bidi of its own.

What it is for

  • Reading notes in a terminal as a page, not as source. Headings get weight and colour instead of # marks, paragraphs keep a readable measure, tables re-flow to the window, callouts, tasks and due dates are drawn as such, and sections fold.
  • Capturing from the shell without opening anything. astrolabe add "call the plumber" appends one line to today’s daily note, and every list verb prints plain lines in a pipe.
  • Living inside an existing vault. Wikilinks, aliases, tags, tasks, callouts and daily notes are read the way Obsidian reads them, and nothing is added to your notes folder: no database, no index on disk.

How to read this manual

Install and First run get you started. Keymap and the shell reference are the references. Vaults and your files says how the vault is chosen and how files are written. Arabic and right-to-left text is for anyone who writes in Arabic, Persian or Hebrew.

MIT licence. Source: github.com/ZahakJ/astrolabe-cli.

Install

Astrolabe CLI is one static binary of about 5.5 MB with no runtime dependencies: no Go, no libc version to match and no Nerd Font. Installing it means putting that file on your PATH.

The one-line install

curl -fsSL https://raw.githubusercontent.com/ZahakJ/astrolabe-cli/main/install.sh | sh

or, where there is no curl:

wget -qO- https://raw.githubusercontent.com/ZahakJ/astrolabe-cli/main/install.sh | sh

The script picks the binary for this machine (astrolabe-linux-amd64, -linux-arm64, -darwin-amd64 or -darwin-arm64) from the latest release, checks it against the release’s checksums.txt, runs it once to make sure it works here, and puts it in ~/.local/bin. It needs no root. It also creates the short alias ast next to it, unless another ast is already on your PATH (it says so) or ASTROLABE_NO_ALIAS=1 is set. At the end it tells you whether ~/.local/bin is on your PATH and, if not, the line that adds it.

On a machine where another program already owns the name ast:

$ curl -fsSL https://raw.githubusercontent.com/ZahakJ/astrolabe-cli/main/install.sh | sh
installed /home/you/.local/bin/astrolabe
  astrolabe v0.2.1 (linux/amd64, go1.22.12)
note: another `ast` is on your PATH (/home/you/.local/bin/ast); the short alias was not created

Try: astrolabe help

Options go after sh -s --, for example … | sh -s -- --version v0.2.0:

OptionEffect
--version vX.Y.Zinstall that release instead of the latest (also ASTROLABE_VERSION)
--bin-dir DIRinstall into DIR instead of ~/.local/bin (also ASTROLABE_BIN_DIR)
--from FILEinstall a binary you already have, offline; a checksums.txt next to it is verified
--uninstallremove the binary and its ast alias
--remove-folioremove a folio binary (the former name) in the same directory

ASTROLABE_BASE_URL points the script at a mirror that holds astrolabe-<os>-<arch> and checksums.txt, for machines that cannot reach GitHub.

By hand

Download one file from the releases page, chmod +x it, and put it anywhere on your PATH.

Check it

astrolabe version
astrolabe doctor

doctor reports what Astrolabe CLI detected about your terminal (colour depth, glyphs, the bidi mode for right-to-left text), which vault it chose and why, and your config file, and ends with a test card of colours and glyphs.

Build from source

You need Go 1.22 or later.

git clone https://github.com/ZahakJ/astrolabe-cli && cd astrolabe-cli
make build          # ./astrolabe, static, stripped
make test           # go vet + go test ./...
make install        # into ~/.local/bin (PREFIX=... to change)
make dist           # all four release binaries and checksums.txt in dist/

scripts/build.sh VERSION is the release build: CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=VERSION" for linux/amd64, linux/arm64, darwin/amd64 and darwin/arm64, then dist/checksums.txt.

Uninstall

sh install.sh --uninstall (or curl … | sh -s -- --uninstall) removes the binary and the alias. Your notes are never touched. The config is in ~/.config/astrolabe-cli/ and the state (recent notes, recovered buffers) in ~/.local/state/astrolabe-cli/; delete those folders to remove every trace.

First run

The guided tour, step by step, with captures. It assumes Astrolabe CLI is installed (see Install).

The repository includes a sample vault, so you can try everything below without touching your own notes. Its dates fall around early October 2026, so which tasks count as “overdue” depends on today’s date.

  1. Install and check. Run astrolabe version, then astrolabe doctor. doctor reports what Astrolabe CLI detected: colour depth, glyphs, bidi, which vault it chose and why, and your config file. It ends with a test card of colours and glyphs.

  2. Point it at notes. Run Astrolabe CLI inside the sample vault (git clone https://github.com/ZahakJ/astrolabe-cli && cd astrolabe-cli/examples/vault), or inside your own Obsidian vault. Any folder that has .obsidian/ or .astrolabe/ above the current directory counts as a vault. Otherwise Astrolabe CLI uses the vault it remembers, else ~/notes. Run astrolabe -C ~/my-notes once and that folder is remembered; astrolabe vault shows which vault is used and why.

  3. Capture a thought from the shell.

    $ astrolabe add "call the plumber about the boiler"
    ✦ Added to daily/2026-10-04.md:3
      - 09:14 call the plumber about the boiler
    $ astrolabe add -t "renew passport due:2026-11-02 !!"
    

    Each capture appends exactly one line to today’s daily note, which is created if needed. Nothing else in the file changes.

  4. Open the reader. Run astrolabe with no arguments. It opens today’s daily note if it exists, otherwise the last note you read, otherwise a home screen. To open a note by name, use astrolabe Home or astrolabe "Lantern migration". A path, title, alias or unique fuzzy match all work. A gold ▎ in the left margin marks the cursor line:

    • j and k move it, and counts work (5j).
    • Ctrl-d and Ctrl-u move half a page; gg and G go to the top and bottom.
    • ]] and [[ jump between headings.
    • za folds the section under the cursor.
  5. Follow a link and come back. Press Tab to highlight the next link and Enter to follow it. Backspace (or Ctrl-o, or H) goes back to the same place, and L goes forward again.

    • A broken link offers to create the note. A broken link to an attachment (![[chart.png]], report.pdf) only says the attachment is missing.
    • A URL is shown and copied to the clipboard (OSC 52). Astrolabe CLI never opens it for you.

  6. Edit and save.

    • Press i on any line and type. The built-in editor opens on that line’s source in Insert mode, at the start of its text (after any list, task, quote or heading marker). a starts typing at the end of the line instead, and e opens the editor in Normal mode.
    • In the editor, Vim keys work as usual: o adds a new line, A appends, u undoes.
    • Press Esc twice to finish. The first Esc leaves Insert mode, and the second saves the file and returns to the reader at the same block.
    • To use your own editor, press E instead. It opens $VISUAL or $EDITOR at that line, and Astrolabe CLI reloads the note when you quit it.

  7. Find a note. Press Ctrl-p (or Space f) to fuzzy-search titles, paths and aliases. With an empty query the finder lists your recent notes. Press Enter to open the selected note.

  8. Search the text. Press Space / and type. Results appear as you type, with titles first, then headings, then body lines, each with its line of context. Press Enter to jump to the hit. Inside the current note, /, n and N search as in Vim.

  9. Tick a task. Put the cursor on a task and press x. Astrolabe CLI rewrites the single character between the brackets in the file and nothing else.

  10. See the agenda. Press Space t to list every open task in the vault, grouped as Overdue, Today, Upcoming and Undated. In the agenda, x toggles a task and Enter jumps to it.

  11. Capture without leaving the page. Press Space c, type, and press Enter. Tab switches between a plain note line and a task.

To learn the rest, press ? for every reader key, or press Space and wait a moment to see the leader keys. Press q to quit.

The keymap lists every key, and the shell reference every verb.

Keymap

Every key in Astrolabe CLI, in the reader, overlays, panels and editor, for printing or keeping in a second window.

The reader keys come from the keymap table in internal/tui/keymap.go. The in-app help (?) and the leader palette (press Space and wait) read the same table.

A count typed before a key repeats it or gives it a target: 5j moves five lines down, and 12G goes to source line 12. Esc always closes the topmost overlay.

Reader

The reader shows an accent-coloured ▎ cursor in the left margin. Actions apply to the block under it.

Move

KeysAction
j, ↓cursor down (counts work: 5j)
k, ↑cursor up
Ctrl-dhalf page down
Ctrl-uhalf page up
Ctrl-f, PgDnpage down
Ctrl-b, PgUppage up
gg, Hometop ({N}gg: source line N)
G, Endbottom ({N}G: source line N)
]], }next heading
[[, {previous heading
l, →scroll a code block right (4 columns)
h, ←scroll a code block left

The cursor skips blank spacing lines.

KeysAction
Tabnext link (highlights it)
Shift-Tabprevious link
Enter, gd, Ctrl-]follow the highlighted link, or the first link on the cursor line
Backspace, Ctrl-o, Hback in history
Lforward in history

What Enter does depends on what is under the cursor:

  • A note link opens the note, and a link to a heading or block jumps there.
  • A broken link asks whether to create the note (y / n). A broken link to an attachment (![[chart.png]]) only says it is missing.
  • A URL is shown in the status bar and copied to the clipboard (OSC 52). Astrolabe CLI never opens it.
  • A heading with no link has its fold toggled.
  • A footnote mark jumps to the footnote’s definition.

Folds, tasks, editing

KeysAction
zafold or unfold the section under the cursor
zcfold the section
zounfold the section
zMfold every section
zRunfold everything
xtoggle the task under the cursor (rewrites one character in the file)
iedit this block in the built-in editor, in Insert mode at the start of the source line’s text (after any list, task, quote or heading marker)
aedit this block in the built-in editor, in Insert mode at the end of the source line
eedit this block in the built-in editor, in Normal mode on the source line
Eopen $VISUAL / $EDITOR at this line, then reload the note. With editor = external, i, a and e do this too

Task states toggle like this:

  • [ ] → [x]
  • [x] → [ ]
  • [/] (in progress) → [x]
  • [-] (cancelled) and any other custom state → [ ]
KeysAction
/search inside the note (incremental; Enter keeps it, Esc restores the view)
nnext match
Nprevious match
Ctrl-pfind a note

Space (leader)

KeysPalette wordAction
Space ffindfind a note (title, path, alias)
Space /, Space ssearchsearch the vault’s text
Space dtodaytoday’s daily note
Space ccapturecapture a line to today’s note
Space nnewnew note
Space tagendaopen tasks by due date
Space #tagstags, then notes with a tag
Space bbacklinksfocus backlinks in the context panel (again: close it)
Space ooutlinefocus the outline in the context panel (again: close it)
Space etreetoggle the file tree
Space rrecentrecent notes
Space Tthemecycle the theme (saved to the config file)
Space yyankcopy this note’s wikilink
Space qquitquit

If you press Space and wait 300 ms, a palette lists every Space binding.

Other

KeysAction
q, ZZquit (asks if there are unsaved edits)
?help
:command line
Escclear the highlighted link and search
Ctrl-lredraw the screen
Ctrl-zsuspend (fg to return)
Ctrl-cshows “type q to quit”; it does not quit

Reader commands

Type these after :. Tab and Shift-Tab complete command names, note names after :e, and theme names after :theme.

CommandAction
:e NOTE, :edit, :o, :openopen a note (name, path or unique fuzzy match)
:ereload the current note from disk
:new [TITLE], :enewcreate a note and open it in the editor (asks for a title if none is given)
:today, :dailytoday’s daily note
:theme [NAME], :colo NAMEswitch theme and save it; with no name, show the current one
:export [FILE]write the note as HTML; the default is <name>.html in the current directory, never inside the vault
:set wrap=N (measure, tw, textwidth)page width in columns, 20 or more
:set nu, :set nonu, :set nu!line numbers in the editor
:setshow the width and theme
:Ngo to source line N
:tagstags overlay
:agenda, :tasksagenda overlay
:nohclear the search highlight
:help, :hhelp overlay
:q, :quit, :wq, :x, :qaquit
:wdoes nothing in the reader, which never changes the file; use i to edit

Overlays and panels

Finder (Ctrl-p, Space f, Space r) and vault search (Space /)

KeysAction
typingfilter (the finder) or search (vault search; runs as you type)
↓, Ctrl-n, Ctrl-jnext result
↑, Ctrl-p, Ctrl-kprevious result
PgDn, Ctrl-d, Ctrl-fpage down
PgUp, Ctrl-u, Ctrl-bpage up
Enteropen the selected note (vault search jumps to the line)
Ctrl-w, Alt-Backspacedelete a word
Ctrl-a, Ctrl-e, ←, →move in the input
Escclose

With an empty query the finder lists recent notes. The note you are reading is listed last, so Ctrl-p Enter returns to the previous note.

Agenda (Space t)

KeysAction
j k, ↓ ↑move
g, Gfirst / last
x, Spacetoggle the task
Enter, lopen the note at the task
q, Escclose

Below 70 columns the agenda drops its note column so the task text gets the whole row; the selected task’s note is named in the header instead.

Tags (Space #)

KeysAction
typingfilter tags
↓ ↑move
Enterlist the notes with that tag

In the list of notes: Enter or l opens the selected note, Backspace or h goes back to the tags, and j/k move.

Capture (Space c) and new note (Space n)

KeysAction
typingthe text
Tab(capture) switch between a note line and a task; typing [ ] first also makes a task
Entersave
Esccancel

Prompts

PromptKeys
Create a note for a broken linky or Enter yes · n or q no
File changed on disk while editingr reload theirs · o overwrite with mine · c cancel

Context panel (Space o, Space b) and file tree (Space e)

KeysAction
j k, ↓ ↑move
g, Gfirst / last
Enter, ljump to the heading, open the backlink or note, or open a folder (tree)
h, ←close a folder (tree)
Ctrl-d, Ctrl-uhalf page (tree)
Esc, Tabreturn focus to the page

At 124 columns or wider, the context panel (outline above backlinks) opens by default.

Help (?)

j k scroll, Ctrl-d Ctrl-u scroll by half a page, g G go to the top and bottom, and q, ? or Esc close it.

Home screen

j k move over the recent notes, and Enter opens one. All the reader’s Space keys and Ctrl-p work here.

Mouse

WhereAction
readerthe wheel scrolls 3 lines. A click places the cursor, follows a link, or toggles a fold chevron
editora click places the cursor; the wheel scrolls
overlaysthe wheel scrolls; clicks are ignored

mouse = off in the config hands the mouse back to the terminal.

Editor

The editor implements a subset of Vim. The status bar shows NORMAL, INSERT, VISUAL or V-LINE. Lines that soft-wrap move by display line with j/k. A count, or an operator, makes them move by source line.

Modes and leaving

KeysAction
i a I AInsert before / after the cursor, at the line’s first non-blank / end
o Oopen a line below / above (on a list item, continues the list)
gIInsert at column 0
v, VVisual, Visual-line
gvreselect the last Visual area
EscInsert/Visual → Normal. In Normal with nothing pending: save if changed, back to the reader
:w, Ctrl-ssave
:qleave (refuses with unsaved changes, as Vim does)
:q!, ZQleave, discarding changes
:wq, :x, ZZsave if changed and leave

In a astrolabe new -o session, leaving the editor exits Astrolabe CLI. Everywhere else it returns to the reader. Other ex commands (:e NOTE, :theme NAME, :today, :new, :export) are passed to the reader.

Motions

KeysMotion
h j k l, arrows, Backspace, Spacecharacter / line
w b e ge, W B E gEwords, WORDS
0 ^ $ g_ {N}|line start, first non-blank, end, last non-blank, column N
gg G {N}G {N}%first / last / Nth line, N percent
{ }paragraph back / forward
%matching bracket
f F t T ; ,find a character on the line, repeat, repeat backwards
H M Ltop / middle / bottom of the window
+ - Enternext / previous line, first non-blank
Ctrl-d Ctrl-u Ctrl-f Ctrl-bhalf / full page
Ctrl-e Ctrl-yscroll one line
zz zt zbcursor line to the middle / top / bottom

Operators and text objects

KeysAction
d c ydelete, change, yank
> <indent, outdent
g~ gu gUtoggle case, lower, upper
iw aw iW aWword, WORD
ip apparagraph
i" a" i' a' i` a`quoted text
i( a( ib · i[ a[ · i{ a{ iB · i< a<brackets (a count selects enclosing levels)

Changes

KeysAction
dd cc yy >> <<the whole line
D C Yto the end of the line (Y as in Neovim)
x Xdelete a character after / before
s Ssubstitute a character / line
r{c}replace a character
J gJjoin lines with / without a space
~toggle case
p Pput after / before
u Ctrl-rundo, redo (an insert session is one step)
.repeat the last change (with a new count if given)
&repeat the last :s

Registers

The editor supports the unnamed register, "0 (last yank), "a–"z (capitals append), "_ (black hole), and "+ / "*, which copy to the system clipboard through the terminal (OSC 52). Text pasted from the terminal arrives as a bracketed paste.

Search and substitute

KeysAction
/pat ?patsearch forward / backward (incremental, smart-case, Vim “magic” syntax)
n Nnext / previous match
* #the word under the cursor, forward / backward
:s/a/b/[flags]substitute on the current line
:%s/a/b/gsubstitute on every line
:N,Ms/…, :'<,'>s/…ranges: N, ., $, %, '<,'>, ±N

The flags are g, i, I and &. In the replacement, &, \0–\9, \r, \n and \t work. Other ex commands: :N, :d, :y, :j, :>, :<, :u, :red, :noh. :set accepts nu, nonu, nu!, wrap=N, hls, ic and scs.

Markdown help in Insert mode

KeysAction
Entercontinue a bullet, numbered item (renumbering the rest), task (new [ ]), quote or callout. On an empty item it ends the list, or outdents a nested item
Tab / Shift-Tab on a list itemindent / outdent it (ordered lists renumber)
Tab / Shift-Tab in a tablenext / previous cell, re-aligning the whole table. Tab in the last cell adds a row
Tab elsewherespaces to the next multiple of 4, or a tab in files indented with tabs
[[note completer: ↓ ↑ or Ctrl-n Ctrl-p move, Enter or Tab insert, Esc keeps the typed text
Ctrl-tinsert YYYY-MM-DD HH:MM

Not in the editor

These Vim features are missing:

  • numbered registers "1–"9 and "-
  • Ctrl-r {reg} in Insert mode, and gi
  • sentence objects is / as
  • macros and marks
  • :set nowrap: the editor always soft-wraps
  • splits
  • back-references inside a search pattern (\1); they do work in :s replacements
  • the c (confirm) flag of :s

Vaults and your files

How notes are stored, how the vault is chosen and remembered, what Astrolabe CLI guarantees about your files, and exactly what it understands in an Obsidian vault.

Astrolabe CLI opens an Obsidian vault as it is. It needs no import and no configuration, and it never writes to .obsidian/. It opens a vault shared with Astrolabe, or any plain folder of .md files, the same way.

Where state lives

The files are the only state. A note is a .md file, and its identity is its path relative to the vault. Its title is the frontmatter title, else its first H1, else the file name. Astrolabe CLI writes outside the vault in only these places:

  • ~/.config/astrolabe-cli/config, which you write yourself; Astrolabe CLI changes only two lines in it: theme, when you change the theme from inside the TUI, and dir, when it remembers your vault (see Vault root).
  • ~/.local/state/astrolabe-cli/recent, which holds recent notes and the last position in each. Losing it loses nothing.
  • ~/.local/state/astrolabe-cli/used, an empty file saying a vault in ~/notes was opened before, so the Welcome note does not open by itself again.
  • ~/.local/state/astrolabe-cli/recovered/, only if Astrolabe CLI is killed with unsaved edits it could not write to the note (see Data safety).

These follow $XDG_CONFIG_HOME and $XDG_STATE_HOME.

Vault root

Astrolabe CLI picks the first of these that applies:

  1. -C DIR
  2. $ASTROLABE_DIR
  3. the nearest parent directory with .obsidian/ or .astrolabe/: the vault you are standing in
  4. the remembered vault (config dir)
  5. ~/notes, if it exists
  6. the current directory, if it holds .md files
  7. otherwise ~/notes, created on the first write

Astrolabe CLI remembers your vault. The first time you run astrolabe -C ~/my-notes it writes dir = /home/you/my-notes to the config file and says Using ~/my-notes from now on; `astrolabe vault` to change. From then on a plain astrolabe opens that folder from any directory. A later -C with a different folder replaces it. A folder found by its .obsidian/ or .astrolabe/ marker is remembered only when nothing is remembered yet, so working inside a project vault never replaces your notes. $ASTROLABE_DIR is never remembered: it wins for that environment only. If the remembered folder is moved or deleted, Astrolabe CLI says so in one line and carries on down the list.

astrolabe vault prints the vault a plain run would use, the rule that chose it and the remembered vault; astrolabe vault DIR remembers another folder and astrolabe vault --forget clears it. astrolabe doctor shows the same, and says when the vault you stand in won over the remembered one.

The Welcome note, a one-minute tutorial, opens by itself only on the very first run: nothing remembered, no vault yet, and ~/notes new and empty. astrolabe learn opens it whenever you want it.

Data safety

  • Writes are atomic. Astrolabe CLI writes a temporary file in the same directory, syncs it and renames it over the original. The file mode is kept.
  • Edits are minimal.
    • Ticking a task rewrites one character.
    • A capture appends one line.
    • Saving from the editor preserves line endings (LF or CRLF), the presence or absence of a final newline, a BOM, the frontmatter and any syntax Astrolabe CLI does not understand.
  • Conflicts are refused. If the file changed on disk after Astrolabe CLI read it, saving does not overwrite it. A prompt offers r to reload theirs, o to overwrite with yours, or c to cancel.
  • Unsaved edits survive a closed window. If Astrolabe CLI is ended by SIGHUP (the terminal window was closed, an SSH connection dropped) or SIGTERM while the editor has unsaved changes, it saves them to the note when the file has not changed on disk since it was read. Otherwise it writes them to ~/.local/state/astrolabe-cli/recovered/<timestamp>-<name>.md, and the next start says where. astrolabe doctor lists recovered buffers until you delete them. Only an explicit discard (:q!, ZQ, “reload theirs”, or answering yes to “Quit anyway?”) throws edits away.
  • New notes never replace existing ones. astrolabe new and Space n add 2, 3 and so on to the name instead.
  • No network access. Astrolabe CLI never touches the network at runtime.

Obsidian compatibility at a glance

Astrolabe CLI reads an Obsidian vault as it is:

  • wikilinks in all forms: [[Note]], [[Note|label]], [[Note#Heading]], [[Note#^block]], [[folder/Note]], all resolved the way Obsidian resolves them
  • aliases, and frontmatter title, tags, aliases, date and created
  • #tags and #nested/tags
  • tasks with [ ], [x], [/] and [-], due dates written 📅 2026-10-05, due:2026-10-05 or @due(2026-10-05), and priorities written !, !! or with the emoji ⏫ 🔼 🔽
  • callouts, including the + and - fold markers
  • embeds: ![[Note]] and ![[Note#Heading]] show the first 12 lines; ![[image.png]] shows a placeholder
  • ==highlights==, footnotes, %% comments %% and math (shown as source)
  • the daily-notes settings in .obsidian/daily-notes.json

Astrolabe CLI does not run plugins, so it has no Dataview, Templater or Canvas. It shows no graph and no images. The rest of this page has the full list.

Finding the vault

If you run Astrolabe CLI anywhere inside a folder that has .obsidian/ above it, that folder is the vault. .astrolabe/ works the same way for vaults that have never seen Obsidian. -C DIR and $ASTROLABE_DIR take precedence over it; the vault you stand in takes precedence over the remembered vault (config dir). The full order is under Vault root above, and astrolabe doctor shows which rule applied.

The scan skips:

  • dot-directories, which includes .obsidian/, .trash/ and .git/
  • node_modules
  • anything matched by a top-level .astrolabeignore

.astrolabeignore works like .gitignore. A pattern without / matches at any depth, a trailing / matches directories only, and ** is supported. Symlinks to files are followed; symlinks to directories are not. Files over 2 MB are indexed by title only.

Notes and titles

A note is any .md file, identified by its path relative to the vault. Other files (images, PDFs, SVGs) are attachments; they can be linked and embedded, and astrolabe doctor counts them.

A note’s title is the first of these that exists:

  1. the frontmatter title
  2. the first H1
  3. the file name without .md

Frontmatter

Astrolabe CLI reads a leading --- YAML block and interprets only these keys:

KeyUse
titlethe note’s title
tags (or tag)tags; either a YAML list or a comma- or space-separated string
aliases (or alias)other names the note answers to, in links, the finder and astrolabe NOTE
date, createdshown in the title block

Every other key is kept as written. In the reader, the extra keys are folded into a ▸ properties · N line, which za opens. Astrolabe CLI never re-serialises the frontmatter. A task toggle or capture never touches it, and the editor saves it byte for byte.

SyntaxUnderstood
[[Note]], [[Note|label]]yes
[[folder/Note]], [[2026/plan]] (folder suffix)yes
[[Note#Heading]], [[Note#Heading#Subheading]]yes; the last heading is used
[[Note#^block-id]] and ^block-id at the end of a blockyes
[[#Heading]] (same note)yes
![[Note]], ![[Note#Heading]]embedded: the first 12 lines in a quote bar, with the note’s title
![[image.png]], ![alt](image.png)shown as ▣ alt with the file name; no image display
[text](relative/path.md), [text](path.md#heading)yes; percent-encoded paths are decoded
[text](https://…), <https://…>, bare https://… and www.…external links
[text][ref] with [ref]: …rendered as a link, but not counted for backlinks or astrolabe links

Resolution

Astrolabe CLI follows Obsidian’s resolution order:

  1. Path match. For a wikilink, Astrolabe CLI first tries an exact relative path from the vault root, then from the linking note’s folder. A Markdown link tries the note’s folder first.
  2. Basename match. Next comes the shortest path whose base name matches. The match ignores case, and .md is optional. An exact case match wins over a case-insensitive one. Remaining ties go to fewer folders, then the shorter path, then alphabetical order.
  3. Aliases.

Attachments must be linked with their extension ([[diagram.svg]]). Heading anchors match while ignoring case and punctuation.

A link that resolves to nothing is broken:

  • In the reader it is drawn in the danger colour with a dotted or curly underline where the terminal supports one, and a plain underline otherwise.
  • Following it offers to create the note. A broken link to an attachment (any target with a file extension other than .md, such as ![[chart.png]] or [[minutes.pdf]]) only reports “missing attachment”: Astrolabe CLI never creates chart.png.md.
  • Listings. astrolabe links lists it with the kind broken, and astrolabe doctor counts broken links across the vault.

Each backlink comes with its context line, shown in:

  • the context panel (Space b)
  • the title block (N backlinks)
  • astrolabe backlinks NOTE

Links from a note to its own headings are not counted.

Tags

Astrolabe CLI reads tags from two places: #tag and #nested/tag in the text, and the frontmatter tags. Nested tags roll up, so a note tagged #project/lantern also counts towards #project.

  • Tags are case-insensitive.
  • Letters from any script are allowed: #فلك is a tag.
  • Purely numeric tags (#2026) are not tags.

A # is not a tag in these places:

  • inside code
  • in a URL or link
  • in a heading marker
  • in a %% comment %% or <!-- comment -->
  • when the character before it is not a space or the start of a line

Tasks

WrittenStateShown
- [ ] textopen☐
- [x] text, - [X] textdone☑, text faint and struck through
- [/] textin progress◐
- [-] textcancelled☒
- [?] text (any single character)custom, counted as open

Due dates can be written three ways: 📅 2026-10-05 (as the Obsidian Tasks plugin writes it), due:2026-10-05 or @due(2026-10-05). The reader shows the date as a chip at the end of the line, such as due 5 Oct, due today, or overdue 1 Oct in the danger colour.

Priorities are !, !!, !!! and the Tasks plugin’s 🔺 ⏫ 🔼 🔽 ⏬. The agenda and astrolabe tasks sort by due date with undated tasks last, then by priority, path and line.

Toggling with x rewrites exactly the one character between the brackets:

  • [ ] → [x]
  • [x] → [ ]
  • [/] → [x]
  • [-] and custom states → [ ]

Astrolabe CLI refuses a toggle in these cases:

  • the line inside the brackets has changed on disk since the note was read
  • the line is no longer a task
  • the line is inside a code fence

Callouts

> [!kind] Title callouts render with a bar, a label line in the callout’s colour, and a glyph. The fold markers work: > [!tip]- starts folded and > [!tip]+ starts open. Toggle either with za.

Kind and aliasesGlyph
note, info
abstract, summary, tldr✎
todo☐
tip, hint, important☞
success, check, done✓
question, help, faq?
warning, caution, attention!
failure, fail, missing, danger, error✗
bug✱
example≡
quote, cite❝

Unknown kinds use the note style.

Other Markdown

SyntaxIn Astrolabe CLI
==highlight==highlighted ground
~~strike~~faint and struck through
%% comment %%, <!-- comment -->hidden in the reader and in exports
footnotes [^1] with [^1]: …[1] markers; definitions gathered at the end; Enter on a marker jumps to it
$inline$, $$display$$ mathshown as source, in the math colour (no LaTeX rendering)
tablesbox-drawing hairlines, alignment from the delimiter row; stacked records when too narrow
fenced coderaised block with a language label; highlighting for sh, python, go, js/ts, json, yaml, c/c++, rust, lua, sql and diff
HTML blocksshown as source, faint; <br> breaks lines

Daily notes

Astrolabe CLI reads folder and format from .obsidian/daily-notes.json. Without that file it uses daily/YYYY-MM-DD.md. The config keys daily_dir and daily_format override both. The supported Moment tokens are YYYY YY MMMM MMM MM M DD D dddd ddd, and text in [brackets] is literal. Month and weekday names are English.

A daily note that Astrolabe CLI creates starts with a heading such as # Sunday, 4 October 2026. Captures (astrolabe add, Space c) append one line each:

  • - HH:MM text for a note
  • - [ ] text for a task

With --at HEADING, the line goes at the end of that section, and Astrolabe CLI adds the heading first if it is missing.

What Astrolabe CLI ignores

  • Plugins. Dataview queries, Templater syntax and other plugin blocks show as ordinary text or code.
  • Canvas. .canvas files and the graph view.
  • Images. They are not displayed.
  • Some syntax. Inline footnotes ^[text], and image sizes such as ![[image.png|100]]; the size stays in the alt text.
  • Parts of the daily-notes settings. The template setting, and Moment week tokens, which are copied literally.
  • Workspace and appearance settings. Everything else under .obsidian/.

Using Astrolabe CLI and Obsidian on the same vault

  • Astrolabe CLI edits files in place with atomic writes and changes only the bytes you changed.
  • Astrolabe CLI adds nothing to the vault apart from the notes and lines you create.
  • If Obsidian (or a sync tool) changes a note while it is open in Astrolabe CLI’s editor, saving does not overwrite it. Astrolabe CLI asks whether to reload, overwrite or cancel.
  • In the reader, Astrolabe CLI reloads a note when you run :e, after E, and after saving. A note changed elsewhere while you are only reading it is not reloaded automatically.

Arabic and right-to-left text

Astrolabe CLI shapes Arabic into joined letter forms and lays it out right to left itself, so Arabic notes read correctly in terminals that do nothing for right-to-left text. Persian and Hebrew go through the same path.

kitty. Bullets, the quote bar and the fold chevron sit on the right edge.

The three bidi modes

Most terminals do no bidi, some do their own, and kitty reverses right-to-left runs while shaping. Each gets a mode.

ModeForWhat Astrolabe CLI does
onterminals that do no bidi: foot, Alacritty, WezTerm, xterm, Windows Terminal and most othersshapes each word into its joined forms and reorders every line into visual order
runskittythe same as on, then writes each stretch of right-to-left letters pre-reversed for kitty to reverse back
offterminals that implement bidi themselves: GNOME Terminal, Tilix and other VTE terminals, Konsole, Apple Terminal, mltermleaves the text in logical order and lets the terminal do the work

The default is auto. It chooses runs in kitty (it looks at TERM, KITTY_WINDOW_ID and TERM_PROGRAM, and inside tmux it asks tmux which terminal is attached), off in the terminals listed above, and on everywhere else. To force one, use --bidi on|off|runs, the config key bidi, or ASTROLABE_BIDI.

Colours, highlights, the selection and the cursor stay on the right cells in every mode.

Check it in your terminal

astrolabe doctor

prints the bidi mode it chose and the reason, and the last line of its test card is an Arabic phrase, which should read joined and right to left. If it does not, try astrolabe doctor --bidi runs, then --bidi on, then --bidi off, and keep the one whose test line reads correctly by setting bidi = … in the config. A terminal that reverses right-to-left runs as kitty does, but is not detected, needs runs.

Where Arabic works

Everywhere text is shown: the reader, the start page, the finder, vault search and in-note /, tags, the agenda, the file tree, the outline and backlinks panel, completion menus, status messages, and the CLI’s own human output (astrolabe render, ls, tasks, find). A paragraph whose first strong character is Arabic or Hebrew is aligned right, with its bullets and bars on the right edge. Right-to-left titles that are too long are cut at their logical end, with the ellipsis on the visual left.

Typing works too. In the finder, search, prompts and the : and / lines, Arabic appears shaped and in visual order as you type, with the caret on the right cell. In the editor’s Insert mode you type Arabic as usual.

Output sent to a pipe is never reordered or shaped: astrolabe find … | grep sees the letters as they are in the file.

Search that understands Arabic

Every search in Astrolabe CLI (finder, astrolabe pick, vault search, in-note /, the tag filter, [[ and :e completion, astrolabe find and ls, and note resolution on the command line) folds Arabic the same way, so you can type the plain form of a word and find every spelling of it:

  • harakat, tatweel and invisible control characters are ignored, so «النُّجوم» finds «النجوم»;
  • أ إ آ ٱ match ا, ى matches ي, ة matches ه, and ؤ and ئ match و and ي;
  • Persian letters match their Arabic counterparts, presentation forms match plain letters, and Arabic-Indic digits match 0 to 9.

Highlights land on the original letters, not on the folded ones. A search written re:… is a literal regular expression and is not folded. The fuzzy finder treats the word after the article and attached particles (ال، وال، بال، لل، و، ب، ل، ف، ك) as a word start, so «اسطرلاب» ranks «الأسطرلاب» high.

Configuration and themes

The six themes, every configuration key with its environment variable and flag, and the other environment variables Astrolabe CLI reads.

Themes

There are six themes: onyx (the default: pure neutral greys with gold as the only accent, on the mark, bullets, links and bars), iron-gall (a warm near-black ground, ivory text and a gold accent), parchment (the light version of iron-gall), graphite (a neutral dark ground with the gold accent), mocha (Catppuccin) and sidereal (a deep blue-black night sky with violet and cyan; in 16 colours it uses magenta and cyan where the others use yellow). Space T cycles the themes, and :theme NAME picks one; both save the choice in the config file.

Configuration

Configuration is optional. The file is ~/.config/astrolabe-cli/config ($XDG_CONFIG_HOME/astrolabe-cli/config). It holds key = value lines, and # starts a comment. Every key can also be set by an environment variable ASTROLABE_<KEY>, which takes precedence over the file. astrolabe doctor warns once about unknown keys.

KeyValues (default)EffectOverride
dirpaththe remembered vault: written by the first -C DIR and by astrolabe vault DIR, used when there is no -C, $ASTROLABE_DIR or vault marker above the working directoryASTROLABE_DIR, -C DIR
themeonyx iron-gall parchment graphite mocha sidereal (onyx)coloursASTROLABE_THEME, --theme
measurecolumns (78)maximum width of the page textASTROLABE_MEASURE, :set wrap=N
groundon off (on)paint the theme’s background at 256 colours and above; off keeps the terminal’s ownASTROLABE_GROUND
bidiauto on off runs (auto)how right-to-left text reaches the terminal: reordered and shaped (on), also pre-reversed for kitty (runs), or untouched (off)ASTROLABE_BIDI, --bidi
mouseon off (on)mouse reporting in the TUIASTROLABE_MOUSE
daily_dirfolder (daily, or Obsidian’s setting)where daily notes liveASTROLABE_DAILY_DIR
daily_formatMoment tokens (YYYY-MM-DD)daily note file name; supports YYYY YY MMMM MMM MM M DD D dddd dddASTROLABE_DAILY_FORMAT
editorbuiltin external (builtin)what i, a and e openASTROLABE_EDITOR
asciion off (off)ASCII glyphs onlyASTROLABE_ASCII, --ascii

Boolean values accept on/off, true/false, yes/no and 1/0. daily_dir and daily_format override .obsidian/daily-notes.json. For example:

# ~/.config/astrolabe-cli/config
dir = ~/notes
theme = parchment
measure = 72
editor = external

Other environment variables:

  • NO_COLOR turns colour off; COLORTERM and TERM are used to detect colour depth.
  • VISUAL, then EDITOR (default vi), choose the external editor.
  • XDG_CONFIG_HOME and XDG_STATE_HOME move the config and state files.
  • ASTROLABE_DEBUG=1 prints the first-frame time on exit and shows timings in the finder and search overlays.
  • The installer reads ASTROLABE_VERSION, ASTROLABE_BIN_DIR and ASTROLABE_BASE_URL; see sh install.sh --help.

Terminals

How Astrolabe CLI adapts to the terminal: colour depths, the ASCII fallback, right-to-left text and the three bidi modes, small windows, tmux and SSH, the mouse, suspend and minimal environments.

  • Colour. Astrolabe CLI detects truecolour, 256, 16 or no colour. Themes are written in 24-bit colour and reduced to the nearest of the 256-colour cube and grey ramp when needed. At 16 colours Astrolabe CLI uses your terminal’s own foreground and background, with yellow as the accent and bold, italic, underline and reverse doing the rest. It paints no backgrounds there. NO_COLOR and TERM=dumb turn colour off. --color forces a level.

  • Glyphs. All glyphs come from common monospace fonts; DejaVu Sans Mono, Menlo and Consolas are the floor. Astrolabe CLI uses no Nerd Font glyphs and no emoji of its own. When the locale is not UTF-8, or with --ascii, an ASCII set takes over: bullets become *, task boxes [ ], bars |, rules -, chevrons v and >, and the ellipsis ....

  • Right-to-left text. A paragraph whose first strong character is Arabic or Hebrew is right-aligned, and its bullets and quote bars move to the right edge. Three modes (--bidi auto|on|off|runs, config bidi):

    • on: terminals with no bidi (foot, Alacritty, WezTerm, xterm, Windows Terminal, …). Astrolabe CLI reorders each line into visual order and shapes Arabic into joined letter forms.
    • runs: kitty, which reverses each stretch of right-to-left letters while shaping. As on, with each such stretch emitted pre-reversed. Colours, highlights and the cursor stay on the right cells.
    • off: terminals that implement bidi themselves (VTE-based ones such as GNOME Terminal and Tilix, Konsole, Apple Terminal, mlterm) get the logical order untouched.

    auto (the default) picks runs in kitty (TERM=xterm-kitty, KITTY_WINDOW_ID, TERM_PROGRAM=kitty; inside tmux, Astrolabe CLI asks tmux which terminal is attached), off in the bidi terminals listed above, and on elsewhere. astrolabe doctor prints the mode it chose and why, and the last line of its test card shows whether Arabic reads joined and right to left. Piped output is never reordered. In runs mode, if the main font itself contains the Arabic letter forms, kitty folds punctuation touching an Arabic word into the same run, so it can land on the other side of the word.

  • Small windows. The page needs 40×10. Below 60 columns the status bar keeps only the note name and the mode. Panels toggled on in a narrow window overlay the page instead of squeezing it. Below the minimum, Astrolabe CLI shows “window too small” until the window grows.

  • tmux and SSH. Astrolabe CLI relies on no passthrough and works in tmux display-popup. Each frame is a diff of changed cells, sent in one write inside synchronized-update marks, so it does not flicker over slow links. Clipboard copies use OSC 52. Inside tmux they need set -g set-clipboard on: with tmux’s default, external, tmux ignores OSC 52 sent by applications. Hyperlinks (OSC 8) are emitted for terminals known to support them, including tmux 3.4 and later.

  • Mouse. Mouse reporting is on only while the TUI runs. The wheel scrolls. In the reader a click places the cursor, follows a link or toggles a fold chevron; in the editor a click places the cursor. To let the terminal handle selection instead, set mouse = off, or hold Shift while selecting, which most terminals honour.

  • Suspend. Ctrl-z suspends Astrolabe CLI and fg brings it back with the screen restored. SIGINT, SIGTERM and SIGHUP restore the terminal before Astrolabe CLI exits, after saving any unsaved editor buffer (see Data safety); an internal crash restores the terminal too.

  • Minimal environments. With LANG=C (or any non-UTF-8 locale) the interface glyphs fall back to the ASCII set, but note text is still read and written as UTF-8, so accented letters, Arabic and CJK in your notes come through unchanged if the terminal can show them. Under TERM=dumb the TUI still draws full-screen, without colour; the shell verbs print plain text there, as in a pipe.

Shell reference

Every shell verb and global flag, the search syntax, exit codes, sample output, and aliases and snippets for your shell.

Verbs

The verbs follow these rules:

  • Results go to stdout and messages to stderr.
  • When stdout is not a terminal, the output is plain and stable: path:line:text or tab-separated columns, and every list verb also accepts --json.
  • In a pipe, paths are relative to the current directory when you are inside the vault, and absolute otherwise, so they always open as printed.
VerbWhat it doesOutput in a pipe
astrolabeopen the reader: today’s daily note, else the last note, else home (the Welcome note on the very first run only)
astrolabe NOTEopen a note by path, title, alias, NOTE:LINE, Note#Heading or unique fuzzy match
astrolabe -page Markdown from stdin (read-only)
astrolabe add TEXT…append - HH:MM text to today’s daily note. Reads stdin if no TEXT is given. -t adds a task, -n NOTE targets another note, --at HEADING adds to the end of that sectionpath:line
astrolabe new TITLE…create a note and print its path. Piped stdin becomes the body. -d DIR picks the folder, -e opens it in $EDITOR, -o in the built-in editorabsolute path
astrolabe todayopen today’s daily note (created if missing). -p prints its path insteadabsolute path
astrolabe find QUERY…full-text search. -l prints paths only, -n N limits results, --jsonpath:line:text
astrolabe ls [QUERY]notes, most recently modified first. --tag T, -l, -n N, --jsonpath⇥YYYY-MM-DD HH:MM⇥title
astrolabe pick [QUERY]interactive fuzzy picker that prints the chosen absolute pathabsolute path
astrolabe tasksopen tasks grouped by due date. --all, --due, --jsonpath:line:[ ] text due:DATE !!
astrolabe tagstags with note counts. -c sorts by counttag⇥count
astrolabe links NOTEoutgoing links, resolved. -l, --jsonline⇥kind⇥destination
astrolabe backlinks NOTEincoming links, each with the line that linkspath:line:context
astrolabe render [FILE|NOTE|-]typeset Markdown: ANSI on a terminal, plain text in a pipe. -w COLS sets the widthtext
astrolabe export NOTEone standalone HTML file. -o FILE writes to a fileHTML
astrolabe path [NOTE]the vault root, or a note’s absolute pathabsolute path
astrolabe vault [DIR]the vault a plain run uses, the rule that chose it and the remembered vault. DIR remembers another folder, --forget clears it. Exit 1 if DIR is missing or not a directoryabsolute path
astrolabe learnopen the Welcome note, a one-minute tutorial, writing Welcome.md into the vault if there is none. -p prints it insteadthe tutorial (-p)
astrolabe doctordetected terminal capabilities, the vault and why (and the remembered vault), config, warnings and a test card
astrolabe help [VERB], astrolabe versionthe manual for one verb; the version

Global flags

These global flags go before or after the verb: -C DIR (or --dir), --theme NAME, --color auto|truecolor|256|16|none, --ascii and --bidi auto|on|off|runs. Astrolabe CLI honours NO_COLOR. The verb names are reserved, so to open a note named after a verb, give its path: astrolabe ./add.md. A first argument that names no note but is close to a verb gets a suggestion before any note matches: astrolabe tsks prints did you mean `astrolabe tasks`?.

Which vault

Astrolabe CLI remembers your vault. The first run with -C DIR writes dir = DIR (absolute) to the config file and prints Using ~/my-notes from now on; `astrolabe vault` to change. on stderr (on a terminal; a script stays quiet). Afterwards a plain astrolabe opens that folder from any directory, and a -C naming another folder replaces it. Each run picks the first that applies:

  1. -C DIR
  2. $ASTROLABE_DIR, which wins for its environment and is never remembered
  3. a folder with .obsidian/ or .astrolabe/ above the current directory: the vault you stand in. It is remembered only when nothing is yet, so a project vault never replaces your notes
  4. the remembered vault (config dir), skipped with a one-line warning if it no longer exists
  5. ~/notes, if it exists
  6. the current directory, if it holds .md files
  7. otherwise a new ~/notes
$ astrolabe -C ~/my-notes ls
Using ~/my-notes from now on; `astrolabe vault` to change.
…
$ cd /tmp && astrolabe vault
✦ ~/my-notes
  chosen by   the remembered vault (config dir)
  remembered  ~/my-notes
$ astrolabe vault --forget
Forgot ~/my-notes; the next `astrolabe -C DIR` is remembered.

$ASTROLABE_DIR, doctor, vault, a vault that does not exist or failed to open, and a single file opened from outside the vault are never remembered. The Welcome note opens by itself only on the very first run (nothing remembered, ~/notes new and empty); astrolabe learn opens it on purpose.

Search syntax

Terms are ANDed. Matching is by substring and smart-case: case is ignored unless a term contains a capital letter. Quote a phrase to match it as one term. Filters:

  • re:PATTERN matches a Go regular expression.
  • tag:x keeps notes tagged x.
  • path:x keeps notes whose path contains x.
  • title:x keeps notes whose title or an alias contains x.

Exit status

CodeMeaning
0success
1nothing found (find, ls, tasks), or no note matches the name
2usage error
130cancelled (Esc in astrolabe pick)

Sample output

Some real output, from the sample vault:

$ astrolabe find cutover | head -3
runbooks/Lantern cutover.md:2:Lantern cutover
runbooks/Lantern cutover.md:7:# Lantern cutover
projects/Lantern migration.md:33:3. **Cutover** — flip consumers queue by queue, smallest first. The

$ astrolabe links "Lantern migration" | head -3
16	embed	attachments/lantern-topology.svg
34	wikilink	runbooks/Lantern cutover.md
41	wikilink	notes/Idempotency keys.md

$ astrolabe links Typography | grep broken
25	broken	Missing note

$ astrolabe tasks | head -3
Typography.md:83:[ ] Transcribe the colophon of the Leiden copy due:2026-02-01
Inbox.md:11:[ ] Renew the staging TLS certificate due:2026-10-02 !!
daily/2026-10-02.md:5:[ ] Renew the staging TLS certificate due:2026-10-02 !!

$ astrolabe find zzz; echo $?
1

In a terminal the same verbs print grouped, aligned and lightly coloured output instead. Lines are shown as the reader shows them, without [[…]], ** or list markers, and Arabic is shaped and reordered when bidi is on; only the piped form above is raw:

$ astrolabe tasks --due
  Overdue · 5
    ☐  Transcribe the colophon of the Leiden copy             1 Feb        Typography
    ☐  Renew the staging TLS certificate !!                   2 Oct        Inbox
    …
  Today · 1
    ☐  Compare plate latitudes with the gazetteer             today        Typography

Aliases and snippets

Aliases and snippets for your shell rc:

alias n='astrolabe add'                   # n "idea"
alias nt='astrolabe add -t'               # nt "reply to the vendor due:2026-10-09"
alias nf='astrolabe find'
np() { f=$(astrolabe pick "$@") && nvim "$f"; }     # pick a note, edit it in Neovim

nvim -q <(astrolabe find deploy)          # every hit in the quickfix list
pbpaste | astrolabe add                   # capture the clipboard (macOS)
git log --oneline -20 | astrolabe new "Release notes draft"
astrolabe ls -l --tag meeting | while IFS= read -r f; do grep -l rollback "$f"; done

To log every commit to your daily note, save this as .git/hooks/post-commit and make it executable:

#!/bin/sh
astrolabe add "$(basename "$(git rev-parse --show-toplevel)"): $(git log -1 --format=%s)" >/dev/null

This writes a line such as - 14:02 astrolabe: Fix the retry budget. A hook runs inside the repository; a remembered vault (see Which vault) beats a repository that merely contains .md files, so the line lands in your notes. A repository with .obsidian/ or .astrolabe/ of its own is a vault and wins; set ASTROLABE_DIR in the hook to be explicit.

Note paths can contain spaces, so use while IFS= read -r f or tr '\n' '\0' | xargs -0 rather than plain xargs (xargs -d '\n' is GNU only; macOS lacks it).

More pipelines, cron jobs, tmux bindings and Neovim mappings are in Shell, tmux and Neovim.

Shell, tmux and Neovim

Shell aliases, pipes and hooks, tmux popups and Neovim. Every verb is in the shell reference.

Three facts behind what follows:

  • Paths. In a pipe Astrolabe CLI prints path:line:text or tab-separated columns. Paths are relative to the current directory when you are inside the vault, and absolute otherwise. new, today -p, path and pick always print absolute paths.
  • The terminal. Interactive parts (astrolabe, astrolabe pick, astrolabe new -o) draw on /dev/tty. So in "$(astrolabe pick)" the picker draws on the terminal and the chosen path goes to stdout.
  • Which vault. Astrolabe CLI remembers your vault: the first astrolabe -C DIR (or astrolabe vault DIR) writes it to the config key dir, and every later run uses it from any directory, including commands started by tmux, Neovim, cron or a git hook. Two things still come before it: ASTROLABE_DIR, and a folder with .obsidian/ or .astrolabe/ above the current directory. astrolabe vault shows which rule applied (full order).

Shell

Aliases and functions

# ~/.bashrc or ~/.zshrc
export ASTROLABE_DIR="$HOME/notes"

alias n='astrolabe add'                      # n "idea"
alias nt='astrolabe add -t'                  # nt "reply to the vendor due:2026-10-09 !!"
alias nf='astrolabe find'                    # nf 'tag:runbook rollback'
alias nl='astrolabe ls -n 10'

# Pick a note and edit it in Neovim. If you cancel the picker (exit 130),
# Neovim does not open.
np() { f=$(astrolabe pick "$@") && nvim "$f"; }

# Create a note and edit it straight away.
nn() { nvim "$(astrolabe new "$@")"; }

# Today's daily note in your editor.
nd() { "${EDITOR:-vi}" "$(astrolabe today -p)"; }

astrolabe new -e TITLE and astrolabe new -o TITLE do the same as nn with $EDITOR and with the built-in editor.

Pipes

# Capture from anything that writes to stdout. Several lines become one
# bullet, with the following lines indented.
pbpaste | astrolabe add                          # macOS clipboard
xclip -o -selection clipboard | astrolabe add    # X11
wl-paste | astrolabe add                         # Wayland

# Append under a heading in a specific note (created if missing).
astrolabe add -n Inbox --at Reading "The Quiet Machine, part II"

# A note from command output.
git log --oneline -20 | astrolabe new "Release notes draft"

# Every search hit in Neovim's quickfix list (path:line:text matches the
# default 'errorformat').
nvim -q <(astrolabe find deploy)

# Paths can contain spaces: read them line by line.
astrolabe ls -l --tag meeting | while IFS= read -r f; do grep -l rollback "$f"; done
astrolabe find -l rollback | tr '\n' '\0' | xargs -0 wc -w  # any xargs

# JSON for scripts.
astrolabe tasks --due --json | jq -r '.[] | select(.group=="overdue") | .text'
astrolabe find --json -n 5 pager | jq -r '.[].abs'

# Render a note for a pager or a mail.
astrolabe render "Lantern migration" | less -R
astrolabe render -w 72 Backpressure > backpressure.txt
astrolabe export Backpressure -o backpressure.html

Exit codes in scripts

CodeMeaning
0success
1nothing found (find, ls, tasks), or no note matches a name
2usage error
130cancelled (Esc in astrolabe pick)

When nothing is found, the message goes to stderr only if stderr is a terminal, so a pipeline stays quiet:

if astrolabe find -l 'tag:oncall pager' >/dev/null; then echo "on-call notes mention the pager"; fi

A commit log in the daily note

Save this as .git/hooks/post-commit in a repository and make it executable (chmod +x). Each commit adds one line, such as - 14:02 astrolabe: Fix the retry budget, to today’s daily note:

#!/bin/sh
astrolabe add "$(basename "$(git rev-parse --show-toplevel)"): $(git log -1 --format=%s)" >/dev/null

To use it in every repository, put it in a template directory (git config --global init.templateDir ~/.git-template, with the file at ~/.git-template/hooks/post-commit) or in a global hooks directory (git config --global core.hooksPath ~/.githooks). The second replaces each repository’s own hooks.

The hook runs inside the repository. With a remembered vault the line goes there, even when the repository contains .md files. With nothing remembered, such a repository would itself be taken as the vault, and a repository with .obsidian/ or .astrolabe/ is always its own vault; set ASTROLABE_DIR in the hook if you need to override either.

Cron or a systemd timer

astrolabe add touches only today’s daily note:

# crontab: a line every weekday at 9:00 with the open overdue count
0 9 * * 1-5  ASTROLABE_DIR=$HOME/notes astrolabe add "overdue: $(astrolabe tasks --json | jq '[.[] | select(.group=="overdue")] | length')"

tmux

Popups

# ~/.tmux.conf

# prefix N: astrolabe in a popup, in the current pane's directory.
bind-key N display-popup -E -w 90% -h 90% -d '#{pane_current_path}' astrolabe

# prefix a / A: capture a line or a task without leaving what you are doing.
bind-key a display-popup -E -w 60% -h 3 -d '#{pane_current_path}' 'printf "capture: "; read -r line && [ -n "$line" ] && astrolabe add -- "$line" >/dev/null'
bind-key A display-popup -E -w 60% -h 3 -d '#{pane_current_path}' 'printf "task: "; read -r line && [ -n "$line" ] && astrolabe add -t -- "$line" >/dev/null'

# prefix F: the finder alone; the chosen note opens in a new window in Neovim.
bind-key F display-popup -E -w 80% -h 80% -d '#{pane_current_path}' 'f=$(astrolabe pick) && tmux new-window nvim "$f"'

Notes on these bindings:

  • Use single quotes. Inside double quotes tmux expands $line itself while it reads the config, so the capture would always be empty.
  • -- ends Astrolabe CLI’s options, so a capture that starts with - is still text.
  • Popups use tmux’s global environment, not your interactive shell’s. If astrolabe is in ~/.local/bin and the popup cannot find it, add set-environment -g PATH "$HOME/.local/bin:$PATH", or write the full path to the binary. A remembered vault needs nothing here (it is in the config file); if you use ASTROLABE_DIR instead, add set-environment -g ASTROLABE_DIR "$HOME/notes" too.
  • Default bindings. N, a, A and F have no default binding in tmux 3.7.
  • The -E flag closes the popup when Astrolabe CLI exits.
  • The -d flag runs Astrolabe CLI in the pane’s directory so that a project vault (.obsidian/ or .astrolabe/ above the pane) is found before the remembered one; elsewhere the remembered vault opens. With ASTROLABE_DIR set it makes no difference.

Clipboard

Astrolabe CLI copies with OSC 52 when you press Space y (the note’s wikilink), follow a URL, or yank to "+ in the editor. tmux accepts OSC 52 from applications only with:

set -g set-clipboard on

With that setting, tmux stores the copy in a paste buffer and passes it on to your terminal if the terminal supports OSC 52. astrolabe doctor run inside tmux prints the same reminder.

Astrolabe CLI detects truecolour from COLORTERM. tmux passes it through if your terminal sets it. If astrolabe doctor inside tmux shows 256 colours while the terminal itself has truecolour, add:

set -as terminal-features ',*:RGB'

Hyperlinks (OSC 8) are emitted inside tmux 3.4 and later.

Neovim

Neovim as Astrolabe CLI’s external editor

When you press E in the reader, Astrolabe CLI runs $VISUAL (else $EDITOR, else vi) through sh with +LINE file, so Neovim opens on the line under the reader’s cursor. When Neovim exits, Astrolabe CLI reloads the note.

To make i (and e, a) open Neovim as well:

# ~/.config/astrolabe-cli/config
editor = external

With editor = external, astrolabe new -e and E behave the same way. The built-in editor is still used for astrolabe new -o.

Editors that take a line number:

EditorArguments Astrolabe CLI passes
vi, vim, nvim, nano, emacs, emacsclient, micro, kak, and others+LINE file
hx (Helix)file:LINE
code, codium, subl, zed-g file:LINE
anything elsefile

A value with arguments, such as EDITOR="emacsclient -t", works because it is run through sh.

Astrolabe CLI inside Neovim

Save this as ~/.config/nvim/lua/astrolabe.lua and add require("astrolabe") to init.lua. It needs Neovim 0.11 or later. On older versions, replace vim.fn.jobstart(cmd, { term = true, … }) with vim.fn.termopen(cmd, { … }).

-- astrolabe inside Neovim: a floating terminal, a picker that edits the chosen
-- note here, and :Capture.
local function float(cmd, on_exit)
  local w = math.floor(vim.o.columns * 0.9)
  local h = math.floor(vim.o.lines * 0.9)
  local buf = vim.api.nvim_create_buf(false, true)
  local win = vim.api.nvim_open_win(buf, true, {
    relative = "editor", style = "minimal", border = "rounded",
    width = w, height = h,
    row = math.floor((vim.o.lines - h) / 2), col = math.floor((vim.o.columns - w) / 2),
  })
  vim.fn.jobstart(cmd, {
    term = true,
    on_exit = function(_, code)
      if vim.api.nvim_win_is_valid(win) then vim.api.nvim_win_close(win, true) end
      if vim.api.nvim_buf_is_valid(buf) then vim.api.nvim_buf_delete(buf, { force = true }) end
      if on_exit then on_exit(code) end
    end,
  })
  vim.cmd.startinsert()
end

-- <leader>nn: the astrolabe reader in a float.
vim.keymap.set("n", "<leader>nn", function() float({ "astrolabe" }) end, { desc = "astrolabe" })

-- <leader>np: pick a note with astrolabe's finder and edit it here.
vim.keymap.set("n", "<leader>np", function()
  local out = vim.fn.tempname()
  float({ "sh", "-c", "astrolabe pick > " .. vim.fn.shellescape(out) }, function(code)
    local path = code == 0 and vim.fn.readfile(out)[1] or nil
    vim.fn.delete(out)
    if path and path ~= "" then
      vim.schedule(function() vim.cmd.edit(vim.fn.fnameescape(path)) end)
    end
  end)
end, { desc = "astrolabe pick" })

-- :Capture some text   appends a line to today's daily note.
vim.api.nvim_create_user_command("Capture", function(o)
  local r = vim.system({ "astrolabe", "add", "--", o.args }, { text = true }):wait()
  vim.notify(r.code == 0 and ("captured " .. vim.trim(r.stdout)) or vim.trim(r.stderr))
end, { nargs = "+" })

How the pieces work:

  • The picker writes the chosen path to a temporary file. Inside a terminal buffer, stdout is the terminal itself, so the path could not be read from it.
  • :Capture runs astrolabe add as a plain process. In a pipe, Astrolabe CLI prints path:line.
  • Nested Neovim. If EDITOR=nvim, pressing E in the floating reader starts a second Neovim inside the float; i uses the built-in editor instead.

More mappings:

-- Today's daily note in the current window.
vim.keymap.set("n", "<leader>nd", function()
  vim.cmd.edit(vim.fn.fnameescape(vim.trim(vim.fn.system({ "astrolabe", "today", "-p" }))))
end, { desc = "astrolabe today" })

-- Search the vault into the quickfix list.
vim.api.nvim_create_user_command("Notes", function(o)
  vim.fn.setqflist({}, " ", { title = "astrolabe find " .. o.args,
    lines = vim.fn.systemlist({ "astrolabe", "find", o.args }) })
  vim.cmd.copen()
end, { nargs = "+" })

:Notes relies on 'errorformat', which matches path:line:text by default. If you run it from outside the vault, Astrolabe CLI prints absolute paths, which also open.

Matching a Catppuccin setup

# ~/.config/astrolabe-cli/config
theme = mocha

mocha uses the Catppuccin Mocha colours: ground #1e1e2e, text #cdd6f4, accent mauve #cba6f7. If you run Neovim and tmux with a transparent background and want Astrolabe CLI to match, add ground = off. Astrolabe CLI then leaves the terminal’s background alone and draws only the text colours.

Dotfiles bootstrap

Install during bootstrap (no root; re-running upgrades in place):

command -v astrolabe >/dev/null 2>&1 ||
  curl -fsSL https://raw.githubusercontent.com/ZahakJ/astrolabe-cli/main/install.sh | sh

Offline, ship the binary and run sh install.sh --from ./astrolabe-linux-amd64. ASTROLABE_VERSION=vX.Y.Z pins a release and ASTROLABE_BIN_DIR=DIR changes the target directory.

Set a theme only if none is set, so a later change survives a re-run. Check for the theme line rather than the file, which Astrolabe CLI creates itself when it remembers a vault:

cfg="${XDG_CONFIG_HOME:-$HOME/.config}/astrolabe-cli/config"
if ! grep -qs '^[[:space:]]*theme[[:space:]]*=' "$cfg"; then
  mkdir -p "$(dirname "$cfg")"
  printf '%s\n' 'theme = mocha' 'editor = external' >> "$cfg"
fi

Astrolabe CLI rewrites the theme = and dir = lines itself and keeps other lines and comments, so do not manage this file as a symlink into a dotfiles repository.

Design

What it is built on and what it leaves out. Implementation notes are in internals.md in the repository.

  • One static binary of about 5.5 MB: the Go standard library plus golang.org/x/sys, x/term, x/text and github.com/rivo/uniseg. The terminal layer, Markdown parser, renderer, highlighter and editor are its own.
  • Plain files. Notes stay Markdown files. No database, no index on disk, nothing written to .obsidian/. Each run scans the vault in memory, and search reads the files on every query. Files over 2 MB are indexed by title only.
  • The note first. The requested note is drawn before the vault is indexed. ASTROLABE_DEBUG=1 prints the first-frame time.
  • A typeset page. Hierarchy comes from weight, colour, spacing and rules, at a measure of 78 columns. The same page works in 16 colours, in ASCII and in a pipe.
  • Reader and editor. The rendered page is for reading, the source for editing, and switching keeps your place. The built-in editor knows Markdown lists, tables and [[ links; E, or editor = external, opens $EDITOR instead.
  • Its own background. At 256 colours and above the theme paints its ground; ground = off keeps the terminal’s. At 16 colours no background is painted.
  • Bidi. Right-to-left text is shaped and reordered by Astrolabe CLI (on), left to terminals that do bidi themselves (off), or pre-reversed for kitty (runs). Editing is always in logical order.
  • Search folds Arabic. Every search ignores harakat and tatweel and unifies letter variants, Persian letters, presentation forms and Arabic-Indic digits. re: patterns stay literal.

Out of scope

Graph view, image display, sync, plugins or scripting, Org-style clocking and recurring tasks, a table or spreadsheet editor, publishing, LaTeX rendering, editor splits, and Vim macros and marks.

Changelog

Every release is on the releases page with four binaries (astrolabe-linux-amd64, -linux-arm64, -darwin-amd64, -darwin-arm64) and checksums.txt. astrolabe version prints the one you have.

ReleasePublished (UTC)
v0.2.19 October 2026
v0.2.04 October 2026
v0.1.04 October 2026, under the former name folio

The notes below are CHANGELOG.md from the repository.

v0.2.1

Remembered vault

  • astrolabe -C ~/my-notes once is enough: the vault is written to the config key dir and stderr says once Using ~/my-notes from now on; `astrolabe vault` to change. A plain astrolabe then opens it from any directory, instead of falling through to an empty ~/notes and the tutorial. A later -C with another folder replaces it; a folder found by its .obsidian//.astrolabe/ marker is remembered only while nothing is. $ASTROLABE_DIR still wins for its environment and is never remembered.
  • New resolution order: -C, $ASTROLABE_DIR, the vault you stand in (marker above the working directory), the remembered vault, ~/notes, the working directory if it holds notes, a new ~/notes. The remembered vault now beats ~/notes and the working-directory guess (before, config dir came before markers). A remembered folder that no longer exists is skipped with a one-line warning.
  • The Welcome note opens by itself only on the very first run: nothing remembered, and ~/notes new and empty. A state file used keeps it from coming back. astrolabe learn opens it on purpose (-p prints it).
  • astrolabe vault [DIR | --forget]: the vault a plain run uses, the rule that chose it and the remembered vault (only the path in a pipe); DIR remembers another folder; --forget clears it.
  • astrolabe doctor shows the remembered vault, says when the vault you stand in won over it, and warns when the config cannot be written or the remembered folder is gone.
  • The status bar names the vault before the breadcrumb, ✦ my-notes › runbooks › Lantern cutover, and the home screen shows it as a home-relative path (~/notes).
  • vault and learn are now reserved words; a note with one of those names opens with astrolabe ./vault.md.

v0.2.0

Renamed: folio is now Astrolabe CLI

  • The command is astrolabe (the installer adds the alias ast; ASTROLABE_NO_ALIAS=1 skips it), the module and repository github.com/ZahakJ/astrolabe-cli, the release assets astrolabe-<os>-<arch>.
  • Environment ASTROLABE_*, config $XDG_CONFIG_HOME/astrolabe-cli/config, state $XDG_STATE_HOME/astrolabe-cli/, vault marker .astrolabe/, ignore file .astrolabeignore.
  • For existing users of the former name: the old folio config and state directories are read when the new ones are absent (nothing is moved), FOLIO_DIR still names the vault, .folio/ still marks a vault and .folioignore is read when .astrolabeignore is absent. The installer reports an old folio binary and removes it only with --remove-folio.

Right-to-left text in kitty

  • Arabic was unreadable in kitty: letters mirrored inside every word. kitty does no bidi, but it lays out each run of right-to-left letters right to left while shaping, which reversed the words it had already put in visual order. A third bidi behaviour, runs, emits each such run pre-reversed so kitty’s own reversal restores it; colours, highlights, selections and the cursor stay on their cells. It is applied last, on the finished screen rows (TUI), on astrolabe render output and on the CLI’s human output.
  • --bidi auto|on|off|runs (config bidi, env ASTROLABE_BIDI). auto picks runs in kitty (TERM, KITTY_WINDOW_ID, TERM_PROGRAM; inside tmux it asks tmux which terminal is attached), off in terminals that do bidi themselves, on elsewhere. astrolabe doctor prints the mode and why.
  • Hamza is shaped into its presentation form, so a shaped word draws all its letters from one font.
  • Input fields (finder, search, prompts, : and /) show Arabic shaped and in visual order, with the caret on the right cell.
  • Right-to-left titles are truncated at their logical end, with the ellipsis on the visual left; Arabic folder names on the home screen, tags, completion menus and status messages are shaped and ordered.

Search that understands Arabic

  • One search folding everywhere (finder, astrolabe pick, vault search, in-note /, tags filter, [[ and :e completion, astrolabe find, astrolabe ls, note resolution): harakat, tatweel and invisible controls are ignored; أ إ آ ٱ match ا, ى matches ي, ة matches ه, ؤ and ئ match و and ي, Persian letters match Arabic ones, presentation forms match letters, Arabic-Indic digits match ASCII digits; Unicode NFC and case folding. Highlights land on the original letters. re: stays literal.
  • Fuzzy ranking treats the word after the article and attached particles (ال، وال، بال، لل، و، ب، ل، ف، ك) as a word start.
  • In-note / search finds words on reordered right-to-left lines and highlights their visual cells.

Look

  • New default theme onyx: pure neutral greys with gold as the only accent. sidereal (deep night sky, violet and cyan) is new too; iron-gall, parchment, graphite and mocha remain.
  • The Astrolabe mark on the home screen, drawn in braille (ASCII with --ascii), and in the README header (docs/logo.svg).
  • Tasks due today or tomorrow show their date in the warning hue.