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
- MTIME is a system field, automatically set to the modification time of the entry's file (YYYYMMDDTHHMMSS format). It updates whenever the entry file changes and cannot be manually edited
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 format: path:1: fields
mado list 'all'
# 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'
# Search across entry bodies with grep
grep 'match' $(mado list -f path 'all')
# Newline-delimited JSON for scripts
mado list -f jsonl 'all'
# 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 |
mtime |
timestamp | >, <, >=, <=, =, !=, ~, !~ |
mtime > 20260505T1230 |
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
Macros start with @ and are resolved at parse time — the value is
substituted before the AST is evaluated.
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'
mado list 'mtime > @now-1'Clone the repository and build:
makeThis will produce an executable file ./mado
| Entries | Peak memory usage |
|---|---|
| 10,000 | 5.3 MB |
| 20,000 | 10.5 MB |
| 100,000 | 51.6 MB |
Time to list 100,043 entries with filter query: ~7.2s
Note: benchmarked on AMD Ryzen 5 5600H (6 cores, 12 threads, 3.30 GHz base) with NVMe SSD
- Unix system (Linux, possibly macOS/BSD)
- Build dependencies:
- C compiler
- C++ compiler
makeflexbison
