Markdown Base CLI (markbase)
A high-performance CLI tool for indexing and querying Markdown notes, designed for both AI agents and human users with Obsidian compatibility in mind.
Installation
From crates.io (recommended):
Build from source:
Prerequisites: Rust 1.85+ (DuckDB is bundled)
Quick Start
Environment Variables
| Variable | Description | Default |
|---|---|---|
MARKBASE_BASE_DIR |
Vault directory | . (current directory) |
MARKBASE_INDEX_LOG_LEVEL |
Automatic indexing output (off, summary, verbose) |
off |
MARKBASE_COMPUTE_BACKLINKS |
Compute file.backlinks during automatic indexing |
disabled |
Priority: CLI args > Environment variables > Defaults
Concepts
Note Properties
Each indexed note has two namespaces for properties:
File Properties (file.* prefix):
Access native database columns representing file metadata:
| Field | Type | Description |
|---|---|---|
file.path |
TEXT | File path relative to base-dir |
file.folder |
TEXT | Directory path relative to base-dir |
file.name |
TEXT | File name without extension |
file.ext |
TEXT | File extension |
file.size |
INTEGER | File size in bytes |
file.ctime |
TIMESTAMPTZ | Created time |
file.mtime |
TIMESTAMPTZ | Modified time |
file.tags |
VARCHAR[] | Tags from content (#tag) and frontmatter |
file.links |
VARCHAR[] | Wiki-links [[link]] + embeds ![[embed]] from body and frontmatter |
file.backlinks |
VARCHAR[] | Notes linking to this note (reverse of links); empty unless backlinks computation is enabled |
file.embeds |
VARCHAR[] | Embeds ![[embed]] from body only |
Note Properties (note.* prefix or bare):
Access YAML frontmatter fields:
---
title: My Note
author: John
status: in-progress
---
Query using explicit prefix or bare shorthand:
Tags
Tags are extracted from two sources:
Content tags (#tag in note body):
- Obsidian format:
#followed by alphanumeric characters, underscores, hyphens, and forward slashes - Must contain at least one non-numerical character (e.g.,
#1984is invalid,#y1984is valid) - Case-insensitive (e.g.,
#tagand#TAGare identical) - Supports nested tags using
/separator (e.g.,#project/2024/q1)
Frontmatter tags:
- YAML list format:
tags: [tag1, tag2]ortags: [project/2024]
All tags are merged into file.tags and can be queried with list_contains(file.tags, 'tag-name').
Field Resolution
| Syntax | Resolves To | Example |
|---|---|---|
file.* |
Native database column | file.name → name column |
note.* |
Frontmatter JSON extraction | note.author → properties->"author" |
| bare (no prefix) | Frontmatter JSON extraction (shorthand for note.*) |
author → properties->"author" |
The file.* and note.* namespaces are completely separate — no naming conflicts.
Name Uniqueness
Note names must be unique across the entire vault, regardless of their directory location.
- Index: When indexing, if two notes have the same name (different paths), a warning is shown and the duplicate is skipped
- Create: Creating a note fails if a note with that name already exists
- Rename: Renaming a note fails if a note with the target name already exists
Link Format (Obsidian Style)
Always use the filename only — no path, no extension:
[[中国移动]]
[[张三]]
[[entities/中国移动.md]]
[[people/张三]]
Wiki-links in frontmatter properties must additionally be wrapped in quotes:
# ✅ Correct
related_customer: "[[中石油]]"
attendees_internal:
# ❌ Wrong
related_customer:
attendees_internal:
Commands
query
Query notes in your vault.
Two input modes:
# Expression mode (WHERE clause only)
# Backlinks are disabled by default to keep indexing fast
# SQL mode (full SELECT statement)
file.backlinks is empty unless backlinks computation is enabled with
--compute-backlinks or MARKBASE_COMPUTE_BACKLINKS.
Default columns for empty input or expression mode: file.path, file.name, description, file.mtime, file.size, file.tags.
Output formats:
-o tablerenders compact Markdown tables-o listrenders YAML lists
- file.name: readme
title: README
file.tags:
- documentation
- important
- file.name: todo
title: Todo List
file.tags:
- todo
- work
Empty results stay machine-friendly:
-o tableprints just the header row and separator-o listprints[]
Debug:
Type casts for non-string comparisons:
# or using bare shorthand:
note
Create and manage notes.
Create a note:
Without a template, markbase note new creates a Markdown note with a default frontmatter field: description: 临时笔记.
Rename a note:
Behavior:
- Looks up note by name (not path)
- Fails if name is ambiguous or new name exists
- Updates all
[[old-name]]links and![[old-name]]embeds across the vault (body and frontmatter) - Preserves aliases, section anchors, and block IDs
- Reindexes the vault immediately after the rename completes
Resolve one or more entity names to notes:
Outputs JSON by default for agent-friendly entity alignment. Each input returns query, status, and matches.
Statuses:
exact— one note matched byfile.namealias— one note matched by frontmatteraliasesmultiple— more than one candidate matched; disambiguate before linkingmissing— no matching note or alias found
Each match includes name, path, type, description, and matched_by. Missing descriptions are emitted as null, not omitted.
A single exact or alias match is still only a low-cost alignment hint: compare description and context before reusing the note. If the description is clearly about a different thing, prefer creating a new note instead of forcing reuse.
Verify a note against its template schema:
Checks that the note conforms to all constraints defined in its referenced MTS template(s), and also runs a global description check before template validation:
- Global frontmatter
descriptionexists, is a string, and is not blank (reported as WARN) - Directory location matches
_schema.location - Required frontmatter fields are present
- Field types and enum values are correct
- Link fields point to notes of the expected
type
Warnings are reported to stderr. Exit code is non-zero only on errors (e.g. missing note or template file).
Render a note (expand .base embeds):
Renders the note body to stdout. Each ![[*.base]] embed is replaced with
query results from the corresponding Obsidian Base file. Non-.base embeds
are passed through unchanged.
For -o table, each rendered Base view becomes a compact Markdown table:
For -o list, the same view is wrapped in a YAML code fence:
```yaml
- -
Supported filters: link(this), link("name"), file.hasLink(this.file),
file.hasTag(), file.inFolder(), date comparisons, isEmpty(), contains().
Warnings (unsupported filters, missing base files) go to stderr. Exit code is non-zero only on hard errors (e.g. note not found).
template
Manage MTS templates.
Templates are stored in templates/ under base-dir. template describe shows the normalized template view used by the CLI, including auto-injected description schema/default fields when older templates omit them.
Query Syntax
markbase translates field names using explicit namespaces (file.* for file metadata, note.* or bare for frontmatter) to DuckDB queries. All DuckDB SQL keywords and operators are supported natively.
Commonly Used Functions:
list_contains(field, value)- Array containmentlist_contains(file.tags, 'todo')- file array field (native)list_contains(note.categories, 'work')- frontmatter array (cast to VARCHAR[])
Field Prefix Reference:
| Prefix | Namespace | Use For | Example |
|---|---|---|---|
file. |
File properties | Metadata columns | file.name, file.mtime, file.size |
note. |
Note properties | Frontmatter fields | note.author, note.status |
| (bare) | Note properties | Shorthand for note.* |
author, status |
Examples:
# File metadata queries (require file.* prefix)
# Frontmatter queries (note.* prefix or bare)
# Combined queries
License
MIT