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:
| Option | Effect |
|---|---|
--version vX.Y.Z | install that release instead of the latest (also ASTROLABE_VERSION) |
--bin-dir DIR | install into DIR instead of ~/.local/bin (also ASTROLABE_BIN_DIR) |
--from FILE | install a binary you already have, offline; a checksums.txt next to it is verified |
--uninstall | remove the binary and its ast alias |
--remove-folio | remove 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.
-
Install and check. Run
astrolabe version, thenastrolabe doctor.doctorreports 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. -
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. Runastrolabe -C ~/my-notesonce and that folder is remembered;astrolabe vaultshows which vault is used and why. -
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.
-
Open the reader. Run
astrolabewith 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, useastrolabe Homeorastrolabe "Lantern migration". A path, title, alias or unique fuzzy match all work. A gold▎in the left margin marks the cursor line:jandkmove it, and counts work (5j).Ctrl-dandCtrl-umove half a page;ggandGgo to the top and bottom.]]and[[jump between headings.zafolds the section under the cursor.
-
Follow a link and come back. Press
Tabto highlight the next link andEnterto follow it.Backspace(orCtrl-o, orH) goes back to the same place, andLgoes 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.
- A broken link offers to create the note. A broken link to an attachment (
-
Edit and save.
- Press
ion 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).astarts typing at the end of the line instead, andeopens the editor in Normal mode. - In the editor, Vim keys work as usual:
oadds a new line,Aappends,uundoes. - Press
Esctwice to finish. The firstEscleaves Insert mode, and the second saves the file and returns to the reader at the same block. - To use your own editor, press
Einstead. It opens$VISUALor$EDITORat that line, and Astrolabe CLI reloads the note when you quit it.
- Press
-
Find a note. Press
Ctrl-p(orSpace f) to fuzzy-search titles, paths and aliases. With an empty query the finder lists your recent notes. PressEnterto open the selected note. -
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. PressEnterto jump to the hit. Inside the current note,/,nandNsearch as in Vim. -
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. -
See the agenda. Press
Space tto list every open task in the vault, grouped as Overdue, Today, Upcoming and Undated. In the agenda,xtoggles a task andEnterjumps to it. -
Capture without leaving the page. Press
Space c, type, and pressEnter.Tabswitches 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
| Keys | Action |
|---|---|
j, ↓ | cursor down (counts work: 5j) |
k, ↑ | cursor up |
Ctrl-d | half page down |
Ctrl-u | half page up |
Ctrl-f, PgDn | page down |
Ctrl-b, PgUp | page up |
gg, Home | top ({N}gg: source line N) |
G, End | bottom ({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.
Links and history
| Keys | Action |
|---|---|
Tab | next link (highlights it) |
Shift-Tab | previous link |
Enter, gd, Ctrl-] | follow the highlighted link, or the first link on the cursor line |
Backspace, Ctrl-o, H | back in history |
L | forward 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
| Keys | Action |
|---|---|
za | fold or unfold the section under the cursor |
zc | fold the section |
zo | unfold the section |
zM | fold every section |
zR | unfold everything |
x | toggle the task under the cursor (rewrites one character in the file) |
i | edit 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) |
a | edit this block in the built-in editor, in Insert mode at the end of the source line |
e | edit this block in the built-in editor, in Normal mode on the source line |
E | open $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 →[ ]
Search
| Keys | Action |
|---|---|
/ | search inside the note (incremental; Enter keeps it, Esc restores the view) |
n | next match |
N | previous match |
Ctrl-p | find a note |
Space (leader)
| Keys | Palette word | Action |
|---|---|---|
Space f | find | find a note (title, path, alias) |
Space /, Space s | search | search the vault’s text |
Space d | today | today’s daily note |
Space c | capture | capture a line to today’s note |
Space n | new | new note |
Space t | agenda | open tasks by due date |
Space # | tags | tags, then notes with a tag |
Space b | backlinks | focus backlinks in the context panel (again: close it) |
Space o | outline | focus the outline in the context panel (again: close it) |
Space e | tree | toggle the file tree |
Space r | recent | recent notes |
Space T | theme | cycle the theme (saved to the config file) |
Space y | yank | copy this note’s wikilink |
Space q | quit | quit |
If you press Space and wait 300 ms, a palette lists every Space binding.
Other
| Keys | Action |
|---|---|
q, ZZ | quit (asks if there are unsaved edits) |
? | help |
: | command line |
Esc | clear the highlighted link and search |
Ctrl-l | redraw the screen |
Ctrl-z | suspend (fg to return) |
Ctrl-c | shows “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.
| Command | Action |
|---|---|
:e NOTE, :edit, :o, :open | open a note (name, path or unique fuzzy match) |
:e | reload the current note from disk |
:new [TITLE], :enew | create a note and open it in the editor (asks for a title if none is given) |
:today, :daily | today’s daily note |
:theme [NAME], :colo NAME | switch 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 |
:set | show the width and theme |
:N | go to source line N |
:tags | tags overlay |
:agenda, :tasks | agenda overlay |
:noh | clear the search highlight |
:help, :h | help overlay |
:q, :quit, :wq, :x, :qa | quit |
:w | does 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 /)
| Keys | Action |
|---|---|
| typing | filter (the finder) or search (vault search; runs as you type) |
↓, Ctrl-n, Ctrl-j | next result |
↑, Ctrl-p, Ctrl-k | previous result |
PgDn, Ctrl-d, Ctrl-f | page down |
PgUp, Ctrl-u, Ctrl-b | page up |
Enter | open the selected note (vault search jumps to the line) |
Ctrl-w, Alt-Backspace | delete a word |
Ctrl-a, Ctrl-e, ←, → | move in the input |
Esc | close |
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)
| Keys | Action |
|---|---|
j k, ↓ ↑ | move |
g, G | first / last |
x, Space | toggle the task |
Enter, l | open the note at the task |
q, Esc | close |
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 #)
| Keys | Action |
|---|---|
| typing | filter tags |
↓ ↑ | move |
Enter | list 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)
| Keys | Action |
|---|---|
| typing | the text |
Tab | (capture) switch between a note line and a task; typing [ ] first also makes a task |
Enter | save |
Esc | cancel |
Prompts
| Prompt | Keys |
|---|---|
| Create a note for a broken link | y or Enter yes · n or q no |
| File changed on disk while editing | r reload theirs · o overwrite with mine · c cancel |
Context panel (Space o, Space b) and file tree (Space e)
| Keys | Action |
|---|---|
j k, ↓ ↑ | move |
g, G | first / last |
Enter, l | jump to the heading, open the backlink or note, or open a folder (tree) |
h, ← | close a folder (tree) |
Ctrl-d, Ctrl-u | half page (tree) |
Esc, Tab | return 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
| Where | Action |
|---|---|
| reader | the wheel scrolls 3 lines. A click places the cursor, follows a link, or toggles a fold chevron |
| editor | a click places the cursor; the wheel scrolls |
| overlays | the 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
| Keys | Action |
|---|---|
i a I A | Insert before / after the cursor, at the line’s first non-blank / end |
o O | open a line below / above (on a list item, continues the list) |
gI | Insert at column 0 |
v, V | Visual, Visual-line |
gv | reselect the last Visual area |
Esc | Insert/Visual → Normal. In Normal with nothing pending: save if changed, back to the reader |
:w, Ctrl-s | save |
:q | leave (refuses with unsaved changes, as Vim does) |
:q!, ZQ | leave, discarding changes |
:wq, :x, ZZ | save 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
| Keys | Motion |
|---|---|
h j k l, arrows, Backspace, Space | character / line |
w b e ge, W B E gE | words, 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 L | top / middle / bottom of the window |
+ - Enter | next / previous line, first non-blank |
Ctrl-d Ctrl-u Ctrl-f Ctrl-b | half / full page |
Ctrl-e Ctrl-y | scroll one line |
zz zt zb | cursor line to the middle / top / bottom |
Operators and text objects
| Keys | Action |
|---|---|
d c y | delete, change, yank |
> < | indent, outdent |
g~ gu gU | toggle case, lower, upper |
iw aw iW aW | word, WORD |
ip ap | paragraph |
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
| Keys | Action |
|---|---|
dd cc yy >> << | the whole line |
D C Y | to the end of the line (Y as in Neovim) |
x X | delete a character after / before |
s S | substitute a character / line |
r{c} | replace a character |
J gJ | join lines with / without a space |
~ | toggle case |
p P | put after / before |
u Ctrl-r | undo, 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
| Keys | Action |
|---|---|
/pat ?pat | search forward / backward (incremental, smart-case, Vim “magic” syntax) |
n N | next / previous match |
* # | the word under the cursor, forward / backward |
:s/a/b/[flags] | substitute on the current line |
:%s/a/b/g | substitute 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
| Keys | Action |
|---|---|
Enter | continue 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 item | indent / outdent it (ordered lists renumber) |
Tab / Shift-Tab in a table | next / previous cell, re-aligning the whole table. Tab in the last cell adds a row |
Tab elsewhere | spaces 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-t | insert YYYY-MM-DD HH:MM |
Not in the editor
These Vim features are missing:
- numbered registers
"1–"9and"- Ctrl-r {reg}in Insert mode, andgi- 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:sreplacements - 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, anddir, 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~/noteswas 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:
-C DIR$ASTROLABE_DIR- the nearest parent directory with
.obsidian/or.astrolabe/: the vault you are standing in - the remembered vault (config
dir) ~/notes, if it exists- the current directory, if it holds
.mdfiles - 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
rto reload theirs,oto overwrite with yours, orcto cancel. - Unsaved edits survive a closed window. If Astrolabe CLI is ended by
SIGHUP(the terminal window was closed, an SSH connection dropped) orSIGTERMwhile 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 doctorlists 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 newandSpace nadd2,3and 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,dateandcreated #tagsand#nested/tags- tasks with
[ ],[x],[/]and[-], due dates written📅 2026-10-05,due:2026-10-05or@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:
- the frontmatter
title - the first H1
- the file name without
.md
Frontmatter
Astrolabe CLI reads a leading --- YAML block and interprets only these keys:
| Key | Use |
|---|---|
title | the 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, created | shown 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.
Links
| Syntax | Understood |
|---|---|
[[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 block | yes |
[[#Heading]] (same note) | yes |
![[Note]], ![[Note#Heading]] | embedded: the first 12 lines in a quote bar, with the note’s title |
![[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:
- 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.
- Basename match. Next comes the shortest path whose base name matches. The match ignores case, and
.mdis optional. An exact case match wins over a case-insensitive one. Remaining ties go to fewer folders, then the shorter path, then alphabetical order. - Aliases.
Attachments must be linked with their extension ([[diagram.svg]]). Heading anchors match while ignoring case and punctuation.
Broken links
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 createschart.png.md. - Listings.
astrolabe linkslists it with the kindbroken, andastrolabe doctorcounts broken links across the vault.
Backlinks
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
| Written | State | Shown |
|---|---|---|
- [ ] text | open | ☐ |
- [x] text, - [X] text | done | ☑, text faint and struck through |
- [/] text | in progress | ◐ |
- [-] text | cancelled | ☒ |
- [?] 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 aliases | Glyph |
|---|---|
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
| Syntax | In 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$$ math | shown as source, in the math colour (no LaTeX rendering) |
| tables | box-drawing hairlines, alignment from the delimiter row; stacked records when too narrow |
| fenced code | raised block with a language label; highlighting for sh, python, go, js/ts, json, yaml, c/c++, rust, lua, sql and diff |
| HTML blocks | shown 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 textfor a note- [ ] textfor 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.
.canvasfiles 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
templatesetting, 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, afterE, 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.
| Mode | For | What Astrolabe CLI does |
|---|---|---|
on | terminals that do no bidi: foot, Alacritty, WezTerm, xterm, Windows Terminal and most others | shapes each word into its joined forms and reorders every line into visual order |
runs | kitty | the same as on, then writes each stretch of right-to-left letters pre-reversed for kitty to reverse back |
off | terminals that implement bidi themselves: GNOME Terminal, Tilix and other VTE terminals, Konsole, Apple Terminal, mlterm | leaves 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.
| Key | Values (default) | Effect | Override |
|---|---|---|---|
dir | path | the 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 directory | ASTROLABE_DIR, -C DIR |
theme | onyx iron-gall parchment graphite mocha sidereal (onyx) | colours | ASTROLABE_THEME, --theme |
measure | columns (78) | maximum width of the page text | ASTROLABE_MEASURE, :set wrap=N |
ground | on off (on) | paint the theme’s background at 256 colours and above; off keeps the terminal’s own | ASTROLABE_GROUND |
bidi | auto 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 |
mouse | on off (on) | mouse reporting in the TUI | ASTROLABE_MOUSE |
daily_dir | folder (daily, or Obsidian’s setting) | where daily notes live | ASTROLABE_DAILY_DIR |
daily_format | Moment tokens (YYYY-MM-DD) | daily note file name; supports YYYY YY MMMM MMM MM M DD D dddd ddd | ASTROLABE_DAILY_FORMAT |
editor | builtin external (builtin) | what i, a and e open | ASTROLABE_EDITOR |
ascii | on off (off) | ASCII glyphs only | ASTROLABE_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_COLORturns colour off;COLORTERMandTERMare used to detect colour depth.VISUAL, thenEDITOR(defaultvi), choose the external editor.XDG_CONFIG_HOMEandXDG_STATE_HOMEmove the config and state files.ASTROLABE_DEBUG=1prints the first-frame time on exit and shows timings in the finder and search overlays.- The installer reads
ASTROLABE_VERSION,ASTROLABE_BIN_DIRandASTROLABE_BASE_URL; seesh 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_COLORandTERM=dumbturn colour off.--colorforces 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-, chevronsvand>, 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, configbidi):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. Ason, 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) picksrunsin kitty (TERM=xterm-kitty,KITTY_WINDOW_ID,TERM_PROGRAM=kitty; inside tmux, Astrolabe CLI asks tmux which terminal is attached),offin the bidi terminals listed above, andonelsewhere.astrolabe doctorprints 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. Inrunsmode, 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 needset -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-zsuspends Astrolabe CLI andfgbrings it back with the screen restored.SIGINT,SIGTERMandSIGHUPrestore 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. UnderTERM=dumbthe 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:textor 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.
| Verb | What it does | Output in a pipe |
|---|---|---|
astrolabe | open the reader: today’s daily note, else the last note, else home (the Welcome note on the very first run only) | |
astrolabe NOTE | open 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 section | path: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 editor | absolute path |
astrolabe today | open today’s daily note (created if missing). -p prints its path instead | absolute path |
astrolabe find QUERY… | full-text search. -l prints paths only, -n N limits results, --json | path:line:text |
astrolabe ls [QUERY] | notes, most recently modified first. --tag T, -l, -n N, --json | path⇥YYYY-MM-DD HH:MM⇥title |
astrolabe pick [QUERY] | interactive fuzzy picker that prints the chosen absolute path | absolute path |
astrolabe tasks | open tasks grouped by due date. --all, --due, --json | path:line:[ ] text due:DATE !! |
astrolabe tags | tags with note counts. -c sorts by count | tag⇥count |
astrolabe links NOTE | outgoing links, resolved. -l, --json | line⇥kind⇥destination |
astrolabe backlinks NOTE | incoming links, each with the line that links | path:line:context |
astrolabe render [FILE|NOTE|-] | typeset Markdown: ANSI on a terminal, plain text in a pipe. -w COLS sets the width | text |
astrolabe export NOTE | one standalone HTML file. -o FILE writes to a file | HTML |
astrolabe path [NOTE] | the vault root, or a note’s absolute path | absolute 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 directory | absolute path |
astrolabe learn | open the Welcome note, a one-minute tutorial, writing Welcome.md into the vault if there is none. -p prints it instead | the tutorial (-p) |
astrolabe doctor | detected terminal capabilities, the vault and why (and the remembered vault), config, warnings and a test card | |
astrolabe help [VERB], astrolabe version | the 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:
-C DIR$ASTROLABE_DIR, which wins for its environment and is never remembered- 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 - the remembered vault (config
dir), skipped with a one-line warning if it no longer exists ~/notes, if it exists- the current directory, if it holds
.mdfiles - 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:PATTERNmatches a Go regular expression.tag:xkeeps notes tagged x.path:xkeeps notes whose path contains x.title:xkeeps notes whose title or an alias contains x.
Exit status
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | nothing found (find, ls, tasks), or no note matches the name |
| 2 | usage error |
| 130 | cancelled (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:textor tab-separated columns. Paths are relative to the current directory when you are inside the vault, and absolute otherwise.new,today -p,pathandpickalways 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(orastrolabe vault DIR) writes it to the config keydir, 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 vaultshows 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
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | nothing found (find, ls, tasks), or no note matches a name |
| 2 | usage error |
| 130 | cancelled (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
$lineitself 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
astrolabeis in~/.local/binand the popup cannot find it, addset-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 useASTROLABE_DIRinstead, addset-environment -g ASTROLABE_DIR "$HOME/notes"too. - Default bindings.
N,a,AandFhave no default binding in tmux 3.7. - The
-Eflag closes the popup when Astrolabe CLI exits. - The
-dflag 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. WithASTROLABE_DIRset 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.
Colour and hyperlinks
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:
| Editor | Arguments 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 else | file |
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.
:Capturerunsastrolabe addas a plain process. In a pipe, Astrolabe CLI printspath:line.- Nested Neovim. If
EDITOR=nvim, pressingEin the floating reader starts a second Neovim inside the float;iuses 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/textandgithub.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=1prints 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, oreditor = external, opens$EDITORinstead. - Its own background. At 256 colours and above the theme paints its ground;
ground = offkeeps 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.
| Release | Published (UTC) |
|---|---|
| v0.2.1 | 9 October 2026 |
| v0.2.0 | 4 October 2026 |
| v0.1.0 | 4 October 2026, under the former name folio |
The notes below are CHANGELOG.md from the repository.
v0.2.1
Remembered vault
astrolabe -C ~/my-notesonce is enough: the vault is written to the config keydirand stderr says onceUsing ~/my-notes from now on; `astrolabe vault` to change.A plainastrolabethen opens it from any directory, instead of falling through to an empty~/notesand the tutorial. A later-Cwith another folder replaces it; a folder found by its.obsidian//.astrolabe/marker is remembered only while nothing is.$ASTROLABE_DIRstill 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~/notesand the working-directory guess (before, configdircame 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
~/notesnew and empty. A state fileusedkeeps it from coming back.astrolabe learnopens it on purpose (-pprints 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);DIRremembers another folder;--forgetclears it.astrolabe doctorshows 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). vaultandlearnare now reserved words; a note with one of those names opens withastrolabe ./vault.md.
v0.2.0
Renamed: folio is now Astrolabe CLI
- The command is
astrolabe(the installer adds the aliasast;ASTROLABE_NO_ALIAS=1skips it), the module and repositorygithub.com/ZahakJ/astrolabe-cli, the release assetsastrolabe-<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
folioconfig and state directories are read when the new ones are absent (nothing is moved),FOLIO_DIRstill names the vault,.folio/still marks a vault and.folioignoreis read when.astrolabeignoreis absent. The installer reports an oldfoliobinary 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), onastrolabe renderoutput and on the CLI’s human output. --bidi auto|on|off|runs(configbidi, envASTROLABE_BIDI).autopicksrunsin kitty (TERM,KITTY_WINDOW_ID,TERM_PROGRAM; inside tmux it asks tmux which terminal is attached),offin terminals that do bidi themselves,onelsewhere.astrolabe doctorprints 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:ecompletion,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,graphiteandmocharemain. - 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.