npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

markart

v0.1.4

Published

CLI tool and JavaScript API for notebook format based on GitHub Flavored Markdown.

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 markart

You can also run it without a global install:

npx markart --help

JavaScript API

Install as a dependency for scripts and integrations:

npm install markart

Quick Start

Create a notebook folder with a README.md file, add a few Markdown notes, then run:

markart maintain

Create a note in daily folder:

markart add daily

Create a note in the current directory when the current directory is the notebook:

markart add .

Check spelling:

markart spellcheck

Features

  • 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}.mdjournal.2025-07-10.md
  • Maintain multiple notebooks at once

Usage

Use it like this:

Create daily note

markart add daily

Create daily note in current directory

markart add .

Regular maintenance

markart maintain  # Optimize all notebooks

Pre-commit check

markart spellcheck  # Check spelling

Format

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.md

Layout 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.md

Optional 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.md

Backlinks 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.

![Mechanic's Notes](.images/2025-07-10-mechanics-notes.jpg)

## 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
  - architecture

Navigation

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 tags field 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:

![Alternative text](.images/image.png)

[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:
  - dehero

locale

Controls language settings for auto-generated blocks date formatting, note title sorting and capitalization. Affects:

Default: en
Type: Any valid BCP 47 language tag (e.g., en, ru, de)

locale: ru # Use Russian month names in backlinks

readmeLinkTitle

Customizes the display text for README link in navigation.

Default: README
Type: string

readmeLinkTitle: Оглавление # Use Russian title

backlinksSectionTitle

Sets the heading text for the auto-generated backlinks collapsible section at the end of the note.

Default: Backlinks
Type: string

backlinksSectionTitle: Ссылки # Use Russian title

adviseLinks

Default: true
Type: boolean

Enables/disables link suggestion system. It connects your notes by automatically inserting links following this simple process:

  1. 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
  2. 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
  3. 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 notebooks

createNoteByLink

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 false for 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:
    1. No content beyond optional H1 heading
    2. No custom tag links
    3. No backlinks from other notes
    4. 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.md always stays in the notebook root
    • all other note files are stored in contentDir
    • .files and .images are stored in contentDir
    • nested paths are allowed, for example notes, content/notes, or wiki/personal
  • Example:
    contentDir: notes

With this config, the notebook layout becomes:

my-notebook/
├── README.md
└── notes/
    ├── .files/
    ├── .images/
    └── my-note.md

dictionary

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:

  1. Navigation block updates
  2. Link optimization between notes
  3. Empty note deletion (when enabled)
  4. Markdown formatting via prettier
  5. 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.yml applies 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 errors

Current limitation: spellchecking is currently configured only for Russian and English.

[!IMPORTANT] spellcheck uses 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.]name

Arguments:

  • <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.

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:

  • Notebook
  • Note
  • 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 folder
  • config: NotebookConfig — effective loaded configuration
  • stats: NoteStats — aggregate parse statistics
  • load(): Promise<void> — load config and notes, parse them, ensure README exists
  • maintain(): AsyncGenerator<NotebookMaintainResult> — run maintenance and yield events
  • spellcheck(): AsyncGenerator<NoteSpellcheckError> — yield spelling issues one by one
  • groupNotes(notes: Set<Note>): NoteGroups — group notes by scope, year, and month
  • compareNotesByTitle(note1, note2): number
  • compareNotesByDate(note1, note2): number

NotebookMaintainResult

Union of maintenance events yielded by Notebook.maintain():

  • DeleteOldEmptyNoteResult
  • AdviseLinksResult

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 found
  • row — one-based row number in the original note after stripped generated blocks/frontmatter are accounted for
  • col — zero-based column reported by the speller
  • word — misspelled word
  • suggestion — 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 notes
  • name: string — filename stem without scope and extension
  • date: Date — note date used in grouping/sorting
  • dictionary: string[] — per-note dictionary
  • isCatalog: boolean — whether the note is a structural notebook entry such as README.md, a scope note, or a tag note
  • tags: TagLink[] — generated tag navigation links
  • backlinks: Set<Note> — notes linking to this note
  • basename: string — filename without extension
  • filename: string — filename with .md
  • fragments: string[] — heading fragments such as #my-title
  • firstReadableLine: string | undefined — title fallback snippet
  • path: string — path inside the notebook folder
  • links: Link[] — content links plus generated scope/tag links
  • targets: Set<Note> — resolved target notes
  • title: string | undefined — first H1 text
  • safeTitle: string — best-effort display title
  • urls: Set<string> — all linked URLs plus the note filename
  • value: 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 matching Key: value with two trailing spaces
  • CodeBlock — line inside a code block, including indented code and fenced code content
  • Fence — code fence delimiter such as ```
  • Heading — ATX heading line starting with #
  • LinkDefinition — reference definition like [ref]: https://example.com
  • Paragraph — regular markdown content line
  • TableSplitter — 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