markart
v0.1.4
Published
CLI tool and JavaScript API for notebook format based on GitHub Flavored Markdown.
Maintainers
Readme
Markart
Markart is a CLI tool and JavaScript API for maintaining notebook-style knowledge bases built from GitHub Flavored Markdown files.
It helps keep Markdown notebooks interconnected, consistently formatted, and easy to browse directly on GitHub.
Requirements
- Node.js 20 or newer
Installation
CLI
Install globally to use the markart command anywhere:
npm install -g markartYou can also run it without a global install:
npx markart --helpJavaScript API
Install as a dependency for scripts and integrations:
npm install markartQuick Start
Create a notebook folder with a README.md file, add a few Markdown notes, then run:
markart maintainCreate a note in daily folder:
markart add dailyCreate a note in the current directory when the current directory is the notebook:
markart add .Check spelling:
markart spellcheckFeatures
- Automatic Navigation Rendering
- Adds link to README.md to every note in the notebook.
- Renders scope and tag links in each note’s generated navigation block.
- Link Management
- Detects and renders links pointing to other notes within the same notebook.
- Suggests and inserts links to relevant notes to maintain interconnectivity.
- Autoformatting
- Utilizes markdownlint and prettier to format notes, ensuring consistency and readability.
- Spell Checking
- Integrates with Yandex.Speller API to check spelling
- Currently supports only Russian and English (
ru,en) - Technical term filtering through dictionaries
- Error output with precise file locations
- Automatic Note Creation
- Automatically generates a new note when a non-existent note is referenced as a URL target in the same Notebook.
- Backlink Analysis
- Identifies notes that lack backlinks, facilitating better note organization and cross-referencing.
- GitHub compatible
- Notebooks maintained using this tool can be accessed and viewed directly through the GitHub interface, eliminating the need for specialized software.
- Automatic Note Grouping
- Notes automatically grouped by year/month in Backlinks sections
- Custom grouping through filename scope prefixes
- File naming features
- Support for date templates via
{YYYY},{MM},{DD} - Automatic current date substitution
- Example:
journal.{YYYY}-{MM}-{DD}.md→journal.2025-07-10.md
- Support for date templates via
- Maintain multiple notebooks at once
Usage
Use it like this:
Create daily note
markart add dailyCreate daily note in current directory
markart add .Regular maintenance
markart maintain # Optimize all notebooksPre-commit check
markart spellcheck # Check spellingFormat
Notebook
Markart notebook is essentially a collection of Markdown files housed in a single folder. Each of these files represents an individual note. By default, notes live in the notebook root. You can also move all notes except README.md, along with .files and .images, into a configured subdirectory via contentDir.
Default layout:
my-notebook/
├── .files/
│ └── reference.pdf
├── .images/
│ └── diagram.png
├── README.md
├── introduction.md
├── features.md
├── tutorial.md
├── tutorial.getting-started.md
└── tutorial.advanced-techniques.mdLayout with contentDir: notes:
my-notebook/
├── README.md
└── notes/
├── .files/
│ └── reference.pdf
├── .images/
│ └── diagram.png
├── introduction.md
├── features.md
├── tutorial.md
├── tutorial.getting-started.md
└── tutorial.advanced-techniques.md[scope.][name].md
Note filename consists of lowercase Latin letters, digits, dashes, and dots, with .md extension at the end. These are all valid note filenames:
2024.md
2024.11-10-sunday.md
journal.2024-11-07.md
night-thoughts.md
my-project.basics-and-concepts.mdOptional scope prefix in filename organizes notes within a notebook into groups. The note [scope].md serves as the entry point for that scope.
Use scope prefixes to create custom groups in more complicated notebooks:
dev.requirements.md
dev.design.md
dev.planning.md
ideas.2025-07-10.mdBacklinks section will automatically show groups:
- Development
- Ideas
README.md
This is a crucial file that should reside in the root of the Notebook folder. It acts as the entry point for navigating the Notebook. README.md always stays in the notebook root, even when contentDir is configured.
.files
Folder to store attachments linked to notes. By default it is stored in the notebook root. When contentDir is set, .files is expected inside that subdirectory.
.images
Folder to store images referenced in notebook notes. By default it is stored in the notebook root. When contentDir is set, .images is expected inside that subdirectory.
.notebook.yml
Notebook configuration file (optional).
Note
Note's content is just simple GitHub Flavored Markdown. Metadata is specified using YAML frontmatter. Note also includes some automatic insertions from Markart to provide navigation between notes. Here's basic example:
---
date: 2025-07-10
tags:
- repair
---
<section id="markart-navigation">
[README](README.md) —
[Car](car.md)
[`Repair`](repair.md)
</section>
# Car Repair Report
**Vehicle:** 2015 Toyota Camry SE
**Mileage:** 85,200 miles
**Service Date:** July 10, 2025
**Service Center:** [QuickFix Auto Garage](https://www.quickfixautosample.com)
## Repair Summary
1. **Replaced brake pads** (front) — Severe wear detected.
2. **Flushed brake fluid** — Contamination found.
3. **Fixed exhaust leak** — Cracked manifold gasket.

## Cost Breakdown
| Item | Quantity | Unit Price | Total |
| ------------------ | -------- | ---------- | ----------- |
| Brake Pads (Front) | 1 set | $45.00 | $45.00 |
| Brake Fluid | 1L | $12.50 | $12.50 |
| Exhaust Gasket | 1 | $28.75 | $28.75 |
| Labor (2.5 hrs) | — | $80/hr | $200.00 |
| **Total** | | | **$286.25** |
## Additional Notes
- Parts sourced from [RockAuto](https://www.rockauto.com) (OEM-equivalent).
- Full inspection report available [here](.files/2025-07-10-full-report.pdf)
- Next service due: **Oil change @ 90,000 miles**.
**Warranty:** 90-day labor warranty on repairs.
<details id="markart-backlinks" open>
<summary>
## Backlinks
</summary>
### Journal
- [July 10, 2025](journal.2025-07-10.md)
</details>Frontmatter
Each note begins with a YAML frontmatter block, which includes metadata about the note:
date: The creation date of the note.tags: An array of tags associated with the note.dictionary: An array of custom words that the spellchecker should ignore for this particular note.
date: 2025-07-10 # Used to sort notes in auto-generated backlinks
dictionary: # Custom words to ignore in spellcheck
- markart
- deheroization
tags: # Automatically converted into tag-links in auto-generated navigation
- web-dev
- architectureNavigation
HTML <section> element with markart-navigation ID that is regenerated by Markart on each run of maintain command. It includes:
- Link to README of the Notebook which the note belongs to.
- Link to scope note if the note is scoped.
- Links to tag notes mentioned in
tagsfield frontmatter.
<section id="markart-navigation">
[README](../README.md) —
[Car](car.md)
[`Repair`](repair.md)
</section>Content
The main content of the note is written using GitHub Flavored Markdown. Attach files and images using the following Markdown syntax:

[file](.files/file.txt)When contentDir is configured, these paths stay the same relative to the note file, but the actual .images and .files folders live inside that configured subdirectory.
Backlinks
HTML <details> element with markart-backlinks ID that is regenerated by Markart on each run of maintain command. It includes links to all notes that link to the current note in their content, excluding those notes that are mentioned in navigation. Use backlinksSectionTitle option to change the title of this block. It is a collapsible, to hide when it's not needed. This block is not generated if the note has no backlinks.
<details id="markart-backlinks" open>
<summary>
## Backlinks
</summary>
### Journal
- [July 10, 2025](journal.2025-07-10.md)
</details>[!WARNING] Don't edit auto-generated blocks, because Markart will rewrite them on next maintenance and all your edits will be lost.
Configuration
The configuration for a Notebook is found in a .notebook.yml file. The tool searches for this configuration file both in the Notebook folder and the current working directory. If a configuration file is present in the Notebook folder, it overrides any found in the working directory for this notebook.
locale: en
readmeLinkTitle: README
backlinksSectionTitle: Backlinks
adviseLinks: true
createNoteByLink: true
deleteOldEmptyNotes: true
contentDir: .
dictionary:
- deherolocale
Controls language settings for auto-generated blocks date formatting, note title sorting and capitalization. Affects:
- Month names in backlinks groupings
- Alphabetic sorting of note titles in backlinks
- Note title capitalization rule for
createNoteByLink
Default: en
Type: Any valid BCP 47 language tag (e.g., en, ru, de)
locale: ru # Use Russian month names in backlinksreadmeLinkTitle
Customizes the display text for README link in navigation.
Default: README
Type: string
readmeLinkTitle: Оглавление # Use Russian titlebacklinksSectionTitle
Sets the heading text for the auto-generated backlinks collapsible section at the end of the note.
Default: Backlinks
Type: string
backlinksSectionTitle: Ссылки # Use Russian titleadviseLinks
Default: true
Type: boolean
Enables/disables link suggestion system. It connects your notes by automatically inserting links following this simple process:
Learn from existing links
The system studies how you've already linked notes together by:- Recording exact phrases you've used as link text
- Remembering which notes you connect to those phrases
Scan for matching phrases
When processing other notes, it:- Looks for exact matches of your previously used phrases
- Only suggests links in text paragraphs and lists (ignores code blocks, headings, tables etc.)
- Limits suggestions to notes in the same scope
Add new links carefully
For each valid match:- Converts the matching text to a clickable link
- Uses the original target note you connected
- Prevents duplicate or overlapping links
What it doesn't do:
- Doesn't understand meaning or context
- Doesn't create new connections beyond your existing patterns
- Doesn't suggest links for similar but non-identical phrases
- Doesn't process headings, code blocks, or tables
Example: If you linked "GraphQL" in one note, it will suggest turning "GraphQL" into a link in other notes - but not "Graph QL" or "gql"
adviseLinks: false # Disable for code-heavy notebookscreateNoteByLink
Controls automatic note creation when linking to non-existent files
- Default:
true - Behavior:
- Creates new note when referencing missing filename
- Generates basic structure:
# [link-text]heading - Files created in same folder as source note
- Warning: Set to
falsefor strict content control - Example workflow:
[New Topic](future-topic.md) # Creates future-topic.md automatically
deleteOldEmptyNotes
Enables automatic cleanup of empty notes
- Default:
true - Deletion criteria:
- No content beyond optional H1 heading
- No custom tag links
- No backlinks from other notes
- Older than 14 days (fixed threshold)
- Exclusion note: README.md and scope notes are never auto-deleted
- Example:
deleteOldEmptyNotes: false # Keep all draft notes indefinitely
contentDir
Moves all notebook notes except README.md into a relative subdirectory. The .files and .images folders are also expected inside the same subdirectory.
- Default:
. - Type:
string - Behavior:
README.mdalways stays in the notebook root- all other note files are stored in
contentDir .filesand.imagesare stored incontentDir- nested paths are allowed, for example
notes,content/notes, orwiki/personal
- Example:
contentDir: notes
With this config, the notebook layout becomes:
my-notebook/
├── README.md
└── notes/
├── .files/
├── .images/
└── my-note.mddictionary
Custom spellcheck dictionary for technical terms/proper nouns
- Format: YAML list of case-insensitive words
- Scope:
- Global: Applies to all notes in notebook
- Combine with per-note dictionaries in frontmatter
- Special characters: Supports words with numbers/dashes (
x86-64) - Recommendations:
- Add project-specific terminology
- Include proper nouns and brand names
- Example:
dictionary: - Kubernetes - GraphQL - neurocache - markart - deheroization
Commands
maintain
Maintains the notebook structure and notes by processing specified folders. Automatic operations include:
- Navigation block updates
- Link optimization between notes
- Empty note deletion (when enabled)
- Markdown formatting via prettier
- Backlinks section regeneration
Usage:
markart maintain [pattern...]Arguments:
[pattern...](Optional): Target folder(s) to operate on (default:.).
Example:
markart maintain # Maintain notebook in current directory
markart maintain * !todo # Maintain all notebook folders except 'todo'
markart maintain projects # Maintain only 'projects' notebook folder
markart maintain projects/* # Maintain all notebook folders inside 'projects'spellcheck
Checks spelling in notebook files and optionally exports errors.
Dictionary management
- Global dictionary in
.notebook.ymlapplies to entire notebook - Local dictionary if established in frontmatter applies to single note along with global dictionary
Output features:
- Formatted console errors:
projects/my-project.md:15:8 Optimization => Optimisation daily/2025-07-10.md:3:12 Coffe => Coffee - Color highlighting (red for errors, green for corrections)
- Technical term filtering through dictionaries
Usage:
markart spellcheck [pattern...] [options]Arguments:
[pattern...](Optional): Folder(s) to check (default:.).
Options:
-o, --output-words <file>: Save misspelled words to a file.
Example:
markart spellcheck # Spellcheck all notes in current directory
markart spellcheck docs -o errors.txt # Spellcheck notes in `docs` and export errorsCurrent limitation: spellchecking is currently configured only for Russian and English.
[!IMPORTANT]
spellcheckuses the external Yandex.Speller API and therefore requires internet access. Network failures, API-side errors, or rate limiting may affect this command.
add
Adds a new note to the specified notebook with optional scope prefix.
Features:
- Auto-generates note with date template when name is omitted:
markart add daily # Creates: daily/2025-07-10.md - Supports scope prefix in name:
markart add daily scope. # Creates: daily/scope.2025-07-10.md - Custom name with scope:
markart add daily scope.my-note # Creates: daily/scope.my-note.md
Usage:
markart add <notebook> [scope.]nameArguments:
<notebook>(Required): Target notebook folder.[scope.]name(Optional): Note name with optional scope prefix (default:{YYYY}-{MM}-{DD}).- If name is
.or ends with., a date template is used as the base name. - If name contains a dot, the part before the dot becomes the scope prefix.
- If name is
Examples:
If you run this on 2025-07-10:
markart add daily # Creates 'daily/2025-07-10.md'
markart add . # Creates './2025-07-10.md' in the current notebook directory
markart add daily my-note # Creates 'daily/my-note.md'
markart add daily . # Creates 'daily/2025-07-10.md'
markart add daily scope. # Creates 'daily/scope.2025-07-10.md'
markart add daily scope.my-note # Creates 'daily/scope.my-note.md'JavaScript API
Markart provides a programmatic API through core classes for scripts, integrations, and custom automation.
Public API stability
The top-level package export (markart) is the supported public API and follows semantic versioning expectations.
Supported top-level exports:
NotebookNote- notebook configuration types and helpers exported from
markart
Modules under markart/dist/lib/* are considered internal implementation details unless explicitly documented otherwise. They may change between minor releases.
Notebook
Notebook extends Map<string, Note> and represents a notebook folder plus all loaded notes.
new Notebook(folder: string)Key fields and methods:
folder: string— notebook root folderconfig: NotebookConfig— effective loaded configurationstats: NoteStats— aggregate parse statisticsload(): Promise<void>— load config and notes, parse them, ensure README existsmaintain(): AsyncGenerator<NotebookMaintainResult>— run maintenance and yield eventsspellcheck(): AsyncGenerator<NoteSpellcheckError>— yield spelling issues one by onegroupNotes(notes: Set<Note>): NoteGroups— group notes by scope, year, and monthcompareNotesByTitle(note1, note2): numbercompareNotesByDate(note1, note2): number
NotebookMaintainResult
Union of maintenance events yielded by Notebook.maintain():
DeleteOldEmptyNoteResultAdviseLinksResult
DeleteOldEmptyNoteResult
{
type: 'deleteOldEmptyNote';
note: Note;
}AdviseLinksResult
{
type: 'adviseLinks';
note: Note;
link: Link;
}NoteSpellcheckError
{
note: Note;
row: number;
col: number;
word: string;
suggestion: string | undefined;
}Semantics:
note— note where the issue was foundrow— one-based row number in the original note after stripped generated blocks/frontmatter are accounted forcol— zero-based column reported by the spellerword— misspelled wordsuggestion— first suggested replacement, if any
Note
Note extends Markdown and represents a parsed notebook note.
new Note(notebook: Notebook, filename: string)Important fields and getters:
scope: CatalogLink | undefined— resolved scope link for scoped notesname: string— filename stem without scope and extensiondate: Date— note date used in grouping/sortingdictionary: string[]— per-note dictionaryisCatalog: boolean— whether the note is a structural notebook entry such asREADME.md, a scope note, or a tag notetags: TagLink[]— generated tag navigation linksbacklinks: Set<Note>— notes linking to this notebasename: string— filename without extensionfilename: string— filename with.mdfragments: string[]— heading fragments such as#my-titlefirstReadableLine: string | undefined— title fallback snippetpath: string— path inside the notebook folderlinks: Link[]— content links plus generated scope/tag linkstargets: Set<Note>— resolved target notestitle: string | undefined— first H1 textsafeTitle: string— best-effort display titleurls: Set<string>— all linked URLs plus the note filenamevalue: string[]— serialized markdown body lines
Key methods:
getContent(): Promise<string | undefined>parse(): Promise<NoteStats | undefined>update(): Promise<void>
NoteFrontMatter
interface NoteFrontMatter {
date?: Date;
dictionary?: string[];
tags?: string[];
[key: string]: unknown;
}LineType
LineType classifies parsed markdown lines:
Attribute— non-standard metadata-style line matchingKey: valuewith two trailing spacesCodeBlock— line inside a code block, including indented code and fenced code contentFence— code fence delimiter such as```Heading— ATX heading line starting with#LinkDefinition— reference definition like[ref]: https://example.comParagraph— regular markdown content lineTableSplitter— table separator row like|---|:---:|TableRow— table row starting with|
Attribute is Markart-specific parser behavior, not a standard Markdown block type.
Example
import { Notebook } from 'markart';
const notebook = new Notebook('notes');
await notebook.load();
for await (const result of notebook.maintain()) {
console.log('Maintenance result:', result);
}
for await (const error of notebook.spellcheck()) {
console.log(`${error.note.filename}:${error.row}:${error.col} ${error.word} => ${error.suggestion ?? '?'}`);
}Use this API for custom scripts, integrations, or automation.
Repository
License
MIT
