Command line
Your notes in the terminal
omajot is one binary. Its commands read and change your notes from a terminal or a script, and it runs the hub and the desktop daemon.
Note commands
These commands work on the notes of this computer. They use the omajot daemon: the one that the plugin started, or, when none runs, one that the command starts in the background (it stops after 10 minutes without use). Changes sync to your other devices like changes that you type in the plugin.
omajot <command> [arguments] [--json] [--data <dir>] [--socket <path>] [--no-start]
Address a note as Folder/Subfolder/Title (the title is the first line of the note, without # ), as only Title, or by its id (n-…, from ls -l or --json). Case does not matter when nothing matches exactly. When a name matches two notes, the command stops with exit code 2 and lists them with their ids.
Changes merge. write, edit, append, replace and restore apply only the parts of the text that change. When your phone changes another part of the same note at the same time, both changes stay.
| Command | What it does |
|---|---|
ls | Lists the folders and notes in a folder. |
cat | Writes the Markdown text of a note to stdout, exactly as stored. |
search | Finds the notes whose title or text contains the text. |
new | Makes a note. |
write | Makes stdin the full text of a note. |
edit | Opens the note in your text editor. |
append | Adds the text to the end of the note, on a new line. |
replace | Replaces the text <old> with <new>. |
mv | Moves the note to the folder. |
rm | Moves the note to the Trash. |
mkdir | Makes the folder and its missing parent folders. |
rmdir | Deletes an empty folder. |
tags | Lists the tags (#word in the text) of the notes that are not in the Trash, with the number of notes, the most used first. |
history | Lists the versions of a note, the oldest first. |
restore | Makes the text of an earlier version the current text of the note. |
export | Writes every note as a Markdown file: <dir>/Folder/Subfolder/Title.md. |
status | Shows the daemon (version, how it started), the data directory, the socket, the hub, and the sync state: online, connecting or offline, and the number of changes that the hub did not get yet. |
omajot ls
Lists the folders and notes in a folder. Without a folder: the top level. Pinned notes come first, then the notes that changed last.
omajot ls [folder] [-r] [-l] [--tag <tag>] [--trash]
| Option | Meaning |
|---|---|
-r, --recursive | Also list the notes in all subfolders, as paths. |
-l, --long | Also show the time of the last change and the id. |
--tag <tag> | Only notes with this tag (with or without #). Includes subfolders. |
--trash | Only notes in the Trash. Includes subfolders. |
$ omajot ls
$ omajot ls Work -r
$ omajot ls --tag todo -l
$ omajot ls --json | jq -r '.notes[].path'
omajot cat
Writes the Markdown text of a note to stdout, exactly as stored.
omajot cat <note> [--at <time>]
| Option | Meaning |
|---|---|
--at <time> | The text as it was at this time: 2026-09-26 14:03 (local time), 2026-09-26T12:03Z (UTC), 2026-09-26, or a time ago: 90s, 15m, 2h, 3d, 1w. |
$ omajot cat "Work/Plans/Q3"
$ omajot cat n-3f9c2a1b7d4e5f60-42
$ omajot cat Groceries --at 2d
omajot search
Finds the notes whose title or text contains the text. Case is ignored. Writes the path of each note, the note that changed last first. Exit code 1 when no note matches.
omajot search <text> [--trash]
| Option | Meaning |
|---|---|
--trash | Search the notes in the Trash instead. |
$ omajot search invoice
$ omajot search "#todo" --json
omajot new
Makes a note. Its text is # <title>, an empty line, and the text. - reads the text from stdin. Writes the path of the new note.
omajot new <title> [text|-] [--folder <folder>]
| Option | Meaning |
|---|---|
--folder <folder> | Put the note in this folder. omajot makes the folder and its parent folders if necessary. |
$ omajot new "Groceries" "- milk"
$ omajot new "Meeting 2026-09-26" --folder Work/Meetings
$ git log -5 --oneline | omajot new "Release notes" - --folder Work
omajot write
Makes stdin the full text of a note. omajot applies only the parts that are different, so changes from other devices in other parts of the note stay. When the note does not exist, omajot makes it: in --folder, or in the folder of the path (Work/Todo goes into Work). When the first line of the text is not the title, omajot adds # <title> at the top, so that the name finds the note again.
omajot write <note> [--folder <folder>] < text
$ omajot cat Todo > todo.md; $EDITOR todo.md; omajot write Todo < todo.md
$ printf '# Status\n\nAll green.\n' | omajot write Work/Status
omajot edit
Opens the note in your text editor. Each time you save, omajot applies your changes to the note, also while other devices change it. omajot applies the last save again when the editor closes. The file is in a private temporary directory, which omajot deletes after the editor closes.
omajot edit <note>
The editor: $VISUAL, else $EDITOR, else vi. The value can have arguments, for example code --wait or nvim -u NONE: omajot runs it as sh -c '<editor> "$1"' omajot <file>. A graphical editor must wait until you close the file (code --wait, subl -w, gedit -s). When no editor is set and vi is not installed, the command fails: set $EDITOR.
$ omajot edit Todo
$ EDITOR="code --wait" omajot edit "Work/Plans/Q3"
omajot append
Adds the text to the end of the note, on a new line. - reads the text from stdin. omajot adds a line break at the end when the text has none.
omajot append <note> <text|->
$ omajot append Groceries "- eggs"
$ date | omajot append Log -
omajot replace
Replaces the text <old> with <new>. <old> must occur exactly one time: when the note does not contain it, exit code 1; when it occurs more than one time, exit code 2 and no change. Add more of the text around <old> to make it unique, or use --all. Case matters. Changes made on other devices at the same time stay.
omajot replace <note> <old> <new> [--all]
| Option | Meaning |
|---|---|
--all | Replace every occurrence. |
$ omajot replace Todo "- [ ] call Anna" "- [x] call Anna"
$ omajot replace Notes "2025" "2026" --all
omajot mv
Moves the note to the folder. / is the top level. The folder must exist (see omajot mkdir).
omajot mv <note> <folder>
$ omajot mv Todo Work
$ omajot mv "Work/Old plan" /
omajot rm
Moves the note to the Trash. omajot does not delete notes: you can get them back from the Trash.
omajot rm <note> [--restore]
| Option | Meaning |
|---|---|
--restore | Move the note out of the Trash. |
$ omajot rm "Old idea"
$ omajot rm "Old idea" --restore
omajot mkdir
Makes the folder and its missing parent folders. A folder that exists is not an error.
omajot mkdir <path>
$ omajot mkdir Work/Meetings/2026
omajot rmdir
Deletes an empty folder. When the folder has notes or folders, the command stops with exit code 3.
omajot rmdir <folder> [--force]
| Option | Meaning |
|---|---|
--force | Delete the folder all the same. Its notes move to the top level and its folders to the parent folder. No note is deleted. |
$ omajot rmdir Work/Old
omajot history
Lists the versions of a note, the oldest first. A version is a group of changes from one device with less than one minute between them. For each version: the number, the time of its last change, the device, and the number of characters added and removed. Show an old text with omajot cat --at, and make it the current text with omajot restore.
omajot history <note>
$ omajot history Todo
$ omajot restore Todo 3
omajot restore
Makes the text of an earlier version the current text of the note. omajot applies it as a normal change: it syncs to your other devices, and the history keeps every version, so you can undo the restore. <version> is a number from omajot history; <time> is written as for cat --at.
omajot restore <note> <version|time>
$ omajot restore Todo 3
$ omajot restore Todo 2h
omajot export
Writes every note as a Markdown file: <dir>/Folder/Subfolder/Title.md. Attachments go to <dir>/attachments/, and the links in the notes still work. <dir>/README.md tells what the files are. The export is a copy: changes to the files do not go back into omajot. omajot changes characters that file names cannot have to -. Notes with the same title get (2), (3), … at the end of the name.
omajot export <dir> [--trash] [--force]
| Option | Meaning |
|---|---|
--trash | Also write the notes in the Trash, to <dir>/Trash/. |
--force | Write into a directory that is not empty. |
$ omajot export ~/notes-backup
omajot status
Shows the daemon (version, how it started), the data directory, the socket, the hub, and the sync state: online, connecting or offline, and the number of changes that the hub did not get yet.
omajot status
$ omajot status
$ omajot status --no-start
omajot tui
A terminal UI for your notes, like the plugin's main window: the folders, the tags and the Trash on the left, the notes in the middle, the note as formatted Markdown on the right. It uses the daemon like the other commands and starts one when none runs. When a note changes on another device, the TUI shows the change at once. The status bar shows the sync state: synced, syncing, connecting, offline, or local only.

omajot tui [--data <dir>] [--socket <path>] [--no-start] [--hub <url> | --no-hub]
| Key | What it does |
|---|---|
j k, ↓ ↑ | Moves down and up in the column. |
h l, ← →, Tab | Goes to the column on the left or right. |
g G | Goes to the first or the last item. |
d u | Scrolls the note down or up. |
/ | Searches the title and text of all notes. Enter keeps the result, Esc clears it. |
e, Enter | Opens the note in your text editor, as omajot edit does. |
n | Makes a note: type the title, then the editor opens. The note goes into the folder that you are in. |
p | Pins or unpins the note. Pinned notes come first. |
x | Moves the note to the Trash. In the Trash, x restores it. |
m | Moves the note to a folder that you select. |
N | Makes a folder, inside the selected folder. |
r | Renames the selected folder. |
? | Shows all keys. |
q | Quits. |
Editing. The TUI gives the terminal to your editor ($VISUAL, else $EDITOR, else vi) and comes back when the editor closes. Each save goes into the note at once. When the note also changes on another device while you edit, omajot keeps both changes and the status bar says Saved and merged. The Omarchy plugin and the web app can have the same note open at the same time.
Pictures show in terminals with the Kitty graphics protocol (Kitty, Ghostty, WezTerm). Other terminals show a line with the name of the picture. Colours come from the current Omarchy theme, else from a built-in dark theme. Set NO_COLOR to use the colours of your terminal. The icons need a Nerd Font, as in Omarchy.
The TUI needs at least 20 × 3 cells. From 110 columns it shows three columns, from 70 columns two, below that one. It gives the terminal back in all cases: when you quit, after an error, and on SIGTERM.
JSON output and exit codes
With --json, every command writes one JSON object to stdout. Scripts and AI agents use it. SKILL.md shows every shape; the most used:
# omajot ls -r --json
{"ok":true,"folder":null,"folders":[],"notes":[{"id":"n-…","title":"Todo","path":"Work/Todo",
"folder":"f-…","tags":["work"],"pinned":false,"trashed":false,"created":1790000000000,
"updated":1790000100000,"snippet":"- call Anna"}]}
# omajot cat Todo --json
{"ok":true,"id":"n-…","path":"Work/Todo","text":"# Todo\n\n- call Anna\n","at":null}
# omajot write / append / replace / new / mv / rm --json
{"ok":true,"id":"n-…","path":"Work/Todo","changed":true,"action":"updated"}
# a failure, here an ambiguous name (exit code 2)
{"ok":false,"exit":2,"error":"ambiguous","candidates":[{"id":"n-…","path":"Work/Todo","trashed":false},…]}
| Exit code | Meaning |
|---|---|
0 | Done. |
1 | Not found: the note, the folder, the text for replace, or no match for search. |
2 | Ambiguous: the name matches more than one note or folder, or replace found the text more than one time. |
3 | Conflict: rmdir of a folder that is not empty, export into a directory that is not empty, or a note that another program has open for editing. |
64 | Usage: a missing or wrong argument. |
69 | No daemon: none runs and none could start (or --no-start). |
70 | Another error. |
omajot hub
Runs the hub: it stores all changes, sends them to your devices, stores attachments and serves the web app.
omajot hub [--port 8787] [--data <dir>] [--login <tailscale login> | --no-auth]
[--web <dir>] [--bind 127.0.0.1] [--url <public url>] [--timeout-ms N]
| Flag | Default | Meaning |
|---|---|---|
--port | hub_port in the config file, else 8787 | The port on the bind address. |
--data | hub_data in the config file, else ~/omajot-data | The folder for batches.jsonl (all changes) and blobs/ (attachments). |
--login | hub_login in the config file | Only requests with this Tailscale-User-Login header may use /api/*. tailscale serve sets the header. |
--no-auth | off | No login check, for local tests. The hub refuses to start with it on an address other than loopback. |
--web | the web app in the binary | Serve the web app from this folder instead, for example web/dist while you work on it. The hub reads the folder when it starts. |
--bind | 127.0.0.1 | The IPv4 address to listen on. Keep loopback when you use tailscale serve. |
--url | hub in the config file if it is an https URL, else from tailscale serve status | The URL for phones. The hub prints it with a QR code when it starts. |
--timeout-ms | 30 000 | The deadline for normal requests, in milliseconds. The event stream has its own deadline of 600 s and ends cleanly 10 s before it. |
The hub needs a login (--login or hub_login) or --no-auth. --no-auth ignores hub_login. With the config file, a plain omajot hub is enough, also for a service. Stop the hub with Ctrl+C: it finishes the open requests first.
omajot daemon
The plugin runs the daemon for you, and the note commands start one when none runs. The daemon keeps the local copy of your notes, syncs it with the hub, and handles paste. It speaks JSON lines on stdin and stdout (for the plugin) and on a unix socket (for the note commands), so you can also use it from scripts.
omajot daemon [--hub <url> | --no-hub] [--data <dir>] [--socket <path>]
[--background [--idle-exit <seconds>]]
| Flag | Default | Meaning |
|---|---|---|
--hub | hub in the config file | The hub URL. Without a hub, the daemon keeps your notes on this computer only. |
--no-hub | off | Do not sync, also when the config file names a hub. |
--data | data in the config file, else ~/.local/share/omajot | The local copy: the log of all changes, the queue of unsent changes, and attachments/. |
--socket | socket in the config file, else $XDG_RUNTIME_DIR/omajot.sock | The socket for the note commands, readable only by you. Other data folders get omajot-<hash>.sock; without $XDG_RUNTIME_DIR (macOS) the socket is <data>/daemon.sock. --socket "" turns it off. |
--background | off | Do not read stdin. The note commands start the daemon like this. A plugin daemon that starts later takes over the data folder. |
--idle-exit | 600 | With --background: stop after this many seconds without note commands (once the hub has all changes). 0: never. |
An example session:
$ omajot daemon --no-hub --data /tmp/notes
{"ev":"sync","state":"offline","pending":0,"head":0}
{"id":1,"cmd":"create","folder":null,"text":"# Hello\nfrom a script #demo"}
{"re":1,"ok":true,"note":"n-c48c3ae4cbe96ad4-1"}
{"ev":"notes","upsert":[{"id":"n-c48c3ae4cbe96ad4-1","title":"Hello","snippet":"from a script #demo","tags":["demo"],…}]}
{"id":2,"cmd":"search","q":"script"}
{"re":2,"ok":true,"ids":["n-c48c3ae4cbe96ad4-1"]}
Each request gets one reply ("re"). Events ("ev") can come at any time.
docs/PROTOCOL.md lists all commands and events. The plugin and the web app use the same commands.
Only one daemon can use a data folder: it locks <data>/daemon.lock. A second daemon on the same folder stops with exit code 3. Use the socket to reach the daemon that runs.
omajot qr
Prints a URL and its QR code in the terminal. Without a URL, it uses hub from the config file. Without a configured hub, it shows how to set one up.
omajot qr [url]
$ omajot qr https://your-mac.your-tailnet.ts.net:8443 Open omajot on your phone: https://your-mac.your-tailnet.ts.net:8443 █▀▀▀▀▀█ ▀█▀ █▀▄ ▀█ █▀▀▀▀▀█ █ ███ █ ██▄▀▀▄ ██▄▀▄▀ █ ███ █ █ ▀▀▀ █ █▀▄▀ ▀▀▄█ █▀ █ ▀▀▀ █ ▀▀▀▀▀▀▀ █ █ █ ▀▄█ ▀▄▀ ▀▀▀▀▀▀▀ █▄██▀▀▀ ▄█▄█ ▀▄▀▄█▄▄█▄█▀█▀▀ ▄ █▄▀▄█▀▄▄▀▀█ ▀▀▄ ▀ █ ▀ ▄▄ ▄ █ ██▄█▀ ▀▀▀▄▄▀▄ ▄█▄▄█▄▄█▄▀▀ ▄ ▀█ ▀▀▀▄ ███ ▀▀▄ ▄▄█ ▀██▀▀▄ █▄ ▄▄█▀▄█ ▄█▀▀▄ █▄█▄▄▄█▄▀█ ▄ █ █▄▄▀▀▀▄██▄▀▄▀▀▀▄ ▀▀ █ ▀▄ ▀ ▀ ▀ ▄ ██▄ ▀█▀▄██▀▀▀█▄███ █▀▀▀▀▀█ ▄█▄ ██▀█▄ ██ ▀ █▀▀ █ ███ █ ██▀ ▄▀▀█▀ █▀▀▀▀▄███ █ ▀▀▀ █ ▀█ ▀▄ ▄▀██▀███▀█ ▀▀▀▀▀▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀ Your phone needs Tailscale. It is free for personal use: https://tailscale.com/download
The QR code is black on white, also in a dark terminal. Phones scan it best at a normal font size.
The config file
~/.config/omajot/config.json (or $XDG_CONFIG_HOME/omajot/config.json). All fields are optional.
{
"hub": "https://your-mac.your-tailnet.ts.net:8443",
"data": "~/.local/share/omajot",
"socket": "/run/user/1000/omajot.sock",
"hub_login": "you@example.com",
"hub_port": 8787,
"hub_data": "~/omajot-data"
}
hub, data and socket are for the daemon, the plugin and the note commands. hub_login, hub_port and hub_data are for omajot hub on the hub computer; its flags win over them. On the hub computer, omajot hub also uses hub as its phone URL, so it does not need to ask Tailscale.
omajot uses the first value it finds:
- the command-line flag (
--hub,--data,--socket), - the plugin setting (
hubUrl,dataDir), when it is not empty, - the config file,
- the default: no hub, and
~/.local/share/omajotfor the data.
Tools
tools/import_joplin.py
Imports a Joplin desktop profile. See Import your Joplin notes.
| Flag | Default | Meaning |
|---|---|---|
--profile | ~/.config/joplin-desktop | The Joplin profile to read. |
--data | ~/.local/share/omajot | The omajot copy to import into. Use a separate folder while the plugin runs. |
--hub / --no-hub | the config file | Where the imported notes go. |
--dry-run | off | Show what the import would do. Change nothing. |
tools/seed_sample.py
Puts the sample notes from examples/sample-notes/ into a new copy: 26 notes in 8 folders, with tags, checklists, tables and images. The screenshots on this site use them.
$ tools/seed_sample.py --data /tmp/omajot-demo-replica --hub http://127.0.0.1:8787