A command-line tool that stores entries (tasks, notes) as markdown files and supports powerful filtering with a query language
- Per-project isolation: Each project has its own
MADO/directory, similar to.git— no global directories are used. This allows you to version‑controlMADO/alongside your code - Entry storage: Entries stored as
MAIN.mdfiles in timestamped directories (YYYYMMDDTHHMMSS/MAIN.md) inMADO/directory. The entry directory can also contain any additional files related to the entry — attachments, screenshots, logs, scripts, etc. Everything stays organized in one place
Start from scratch in a new project:
# Create a project directory and enter it
mkdir myproject
cd myproject
# Initialize MADO directory (like git init)
mado initOnce the MADO/ directory is initialized, you can work with entries
from any subdirectory within the project — just like Git, mado
automatically finds the nearest MADO/ directory by walking up the
file tree
# Create your first entry
mado newThe entry will be created with the following content:
- NAME:
- PRIORITY:
- TAGS:
- STATUS:
- DEADLINE:
You can fill it out as needed, for example:
- NAME: Fix login bug
- PRIORITY: 10
- TAGS: bug, critical, auth
- STATUS: opened
- DEADLINE: 20260615
The login page returns 500 error when using special characters.
...
No fields are required — you can omit any field entirely or leave its value empty. For example, when writing a note, you probably won't need the priority, status and deadline fields. When a field is omitted or left empty:
- NAME, STATUS default to empty string ""
- PRIORITY defaults to 0
- DEADLINE defaults to 99990000T000000
- TAGS defaults to a list with one empty element [""]
- TIME is a system field, always present and set to the entry's directory name (creation timestamp in YYYYMMDDTHHMMSS format). It cannot be changed or removed — it reflects when the entry was created
When you have many entries, you'll want to filter them:
# List all entries
mado list 'all'
# Find critical bugs
mado list 'tag = bug and priority > 5'
# Delete low priority entries
mado remove 'priority < 5'
# Filter by entry name (exact match)
mado list 'name = login'
# Filter by entry name (substring)
mado list 'name ~ login'
mado list 'name ~ "fix login"' # multiple words — quotes required
# Filter by creation time
mado list 'time ~ 20260516' # Entries created on 2026-05-16
mado list 'time > 20260516T12' # Entries created after 2026-05-16 12:00:00
mado list 'time > 2023 and time < @year+1' # Entries created between 2023 and current year (inclusive)
# Find entries with complex conditions
mado list '(tag = bug or tag = critical) and status = opened and deadline < @now'
mado list 'not (priority < 3 or status = closed)'
mado list '(priority > 5 and tag = urgent) or status = reopened'
# Syntactic sugar for matching multiple values
mado list 'status = anyof(opened, reopened)' # Status equals "opened" OR "reopened"
mado list 'priority = anyof(10, 20, 30)' # Priority equals 10 OR 20 OR 30
mado list 'tag = allof(bug, critical)' # Entry has BOTH "bug" AND "critical" tags
# Sort results
mado list -s +priority 'tag = bug' # bugs sorted by priority
mado list -s -priority,+time 'all' # highest priority first, newest firstTemplates are stored in MADO/.templates/ as markdown files:
# Create a bug report template
cat > MADO/.templates/bug.md << 'EOF'
- NAME:
- PRIORITY: 10
- TAGS: bug
- STATUS: opened
## Steps to Reproduce
1.
## Expected Behavior
## Actual Behavior
EOFYou can create entries with custom templates:
mado new -t bug
# The template must exist at: MADO/.templates/bug.mdThe -s / --sort flag allows you to sort entries by one or more fields:
# Sort by priority (ascending by default)
mado list -s priority 'all'
# Sort by priority descending
mado list -s -priority 'all'
# Sort by multiple fields (priority ascending, then time descending)
mado list -s +priority,-time 'all'
# Sort by status, then name
mado list -s status,name 'all'Sort order:
+fieldorfield— ascending order (default)-field— descending order
The -f flag controls how entries are displayed:
# Default unix format: path:1: fields
mado list 'all'
MADO/20260521T204844/MAIN.md:1: TIME:[20260521T204844] NAME:[...] PRIORITY:[10] DEADLINE:[99990000T000000] STATUS:[closed] TAGS:[feat,flag]
# Compatible with Emacs compile buffer and other tools that parse file:line:
# Paths only — useful for piping to other tools
mado list -f path 'all'
# MADO/20260516T161611/MAIN.md
# Search across entry bodies with grep
grep 'match' $(mado list -f path 'all')
# Newline-delimited JSON for scripts
mado list -f jsonl 'all'
# {"time":"20260521T204844","name":"...","priority":10,"deadline":"99990000T000000","status":"closed","tags":["feat","flag"],"path":"MADO/20260521T204844/MAIN.md"}
# Pipe JSON output to jq for advanced processing
mado list -f jsonl 'priority > 5' | jq '.name'
mado list -f jsonl 'all' | jq -s 'group_by(.status)'
mado list -f jsonl 'all' | jq -s 'sort_by(.priority)'The query language supports filtering entries using operators and keywords
| Operator | Description |
|---|---|
> |
Greater than |
< |
Less than |
>= |
Greater than or equal |
<= |
Less than or equal |
= |
Equal / exact match |
!= |
Not equal |
~ |
Contains |
!~ |
Not contains |
| Operator | Description |
|---|---|
and |
Logical AND |
or |
Logical OR |
not |
Logical NOT |
| Type | Description | Format | Examples |
|---|---|---|---|
| number | Integer value | 0-999 | 0, 10, 999 |
| string | Text value | Unquoted: [a-zA-Z_][a-zA-Z0-9_-]* or quoted: "..." or '...' |
bug, "fix login", 'проблема' |
| timestamp | Creation time of entry | 4 digits, 6 digits or (8 digits + optional (T + 0, 2, 4 or 6 digits)) |
2026, 20260516, 20260516T, 20260516T1230 |
| Keyword | Type | Operators | Example |
|---|---|---|---|
priority |
number | >, <, >=, <=, =, !=, ~, !~ |
priority > 5, priority != 10 |
tag |
string | >, <, >=, <=, =, !=, ~, !~ |
tag = bug, tag ~ crit |
status |
string | >, <, >=, <=, =, !=, ~, !~ |
status = opened, status ~ open |
name |
string | >, <, >=, <=, =, !=, ~, !~ |
name = "Fix bug", name ~ login |
time |
timestamp | >, <, >=, <=, =, !=, ~, !~ |
time > 20260505T1230 and time < 20260510T |
deadline |
timestamp | >, <, >=, <=, =, !=, ~, !~ |
deadline > 20260505T1230 and deadline < 20260510T |
all |
special | - | all |
Note: all operators also work with
anyof(...)andallof(...). These are syntactic sugar that expand to multiple conditions.anyof(...)expands withor,allof(...)expands withand. Examples:
status = anyof(opened, reopened)is equivalent tostatus = opened or status = reopenedtag ~ allof(bug, crit, fix)is equivalent totag ~ bug and tag ~ crit and tag ~ fix
Query language supports macros that expand to values at execution
time. Macros start with @
Resolve to timestamps. Accept optional offsets with +/-
| Macro | Resolution | Offset unit |
|---|---|---|
@now |
Current timestamp | days |
@today |
Start of today | days |
@week |
Start of current week (Monday) | weeks |
@month |
Start of current month | months |
@year |
Start of current year | years |
Examples:
mado list 'deadline > @week'
mado list 'deadline > @year'
mado list 'time > @today-4'
mado list 'time >= @week-1'
mado list 'deadline < @today+30'
mado list 'time ~ @month-2'
Clone the repository and build:
make
This will produce an executable file ./mado
- Unix system (Linux, possibly macOS/BSD)
- Build dependencies:
- C compiler
- C++ compiler
makeflexbison
