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

@markdownai/parser

v0.0.10

Published

Pure AST parser for MarkdownAI documents. Reads `.md` source and returns a typed AST with no side effects - no execution, no IO, no filesystem access.

Downloads

554

Readme

@markdownai/parser

Pure AST parser for MarkdownAI documents. Reads .md source and returns a typed AST with no side effects - no execution, no IO, no filesystem access.

All packages: @markdownai/core  ·  @markdownai/engine  ·  @markdownai/parser  ·  @markdownai/renderer  ·  @markdownai/mcp  ·  @markdownai

Links: GitHub  ·  npm org


What it does

@markdownai/parser is the first stage of the MarkdownAI toolchain. It reads a MarkdownAI document line by line and produces a complete, typed AST (abstract syntax tree) describing every directive, block, inline expression, and plain markdown node in the file.

It is completely inert. Parsing a document has no side effects - nothing is fetched, no files are read, no environment variables are touched. This makes it safe to use in any context, including editors, linters, and static analysis tools.

Every mai command uses this parser internally. You'd reach for it directly if you need to analyze or transform MarkdownAI source without running it.

Installation

npm install @markdownai/parser

Requires Node.js >= 18.

Usage

import { parse } from '@markdownai/parser'

const source = `@markdownai

@env DATABASE_URL fallback="postgres://localhost:5432/mydb"

# Status Report

Files in src: @count ./src/ match="**/*.ts"
`

const ast = parse(source)

// ast.header - the @markdownai declaration node
// ast.nodes  - array of typed directive and markdown nodes

The @markdownai Header

A MarkdownAI document must start with @markdownai on line 1. If the header is missing, the file is treated as plain Markdown - the parser returns a plain AST with no directive nodes.

You can pin a version: @markdownai v1.0. The parser records the version in the header node.

const ast = parse('@markdownai v1.0\n\n# My Doc\n')
console.log(ast.header.version) // "1.0"

Node Types

The parser produces a flat array of typed nodes. Every node has a type field as a discriminant.

Directive nodes

| Type | Directive | Description | |------|-----------|-------------| | EnvNode | @env | Environment variable declaration with optional fallback | | QueryNode | @query | Shell command execution | | HttpNode | @http | HTTP request | | DbNode | @db | Database query block | | ConnectNode | @connect | Database connection registration | | ListNode | @list | Filesystem or structured data listing | | ReadNode | @read | Structured file read | | TreeNode | @tree | Directory tree rendering | | DateNode | @date | Date/timestamp injection | | CountNode | @count | File count | | IncludeNode | @include | Content inclusion | | ImportNode | @import | Definition import | | DefineNode | @define | Macro definition block | | CallNode | @call | Macro invocation | | ConditionalNode | @if / @elseif / @else / @endif | Conditional block with branches | | PhaseNode | @phase | Phase block with @on complete transitions | | PipeNode | source | transform | @render | Pipe chain | | RenderNode | @render | Render sink with format type | | PromptNode | @prompt | AI instruction block | | NoteNode | @note | Human-readable source comment (stripped by default) | | SectionNode | @section | Context budget priority section | | MarkdownNode | (plain text) | Non-directive markdown content | | InterpolationNode | {{ expression }} | Inline expression | | ShellInlineNode | !`command` | Shell inline (Claude Code syntax) |

Block structure

Block directives (@define, @phase, @if, @prompt, @note, @section) open with the directive and close with @end or @endif. The parser tracks nesting and returns each block as a single node with its children.

const ast = parse(`@markdownai

@define greeting(name)
Hello, {{ name }}!
@end
`)

const defineNode = ast.nodes.find(n => n.type === 'DefineNode')
// defineNode.name     - "greeting"
// defineNode.params   - ["name"]
// defineNode.body     - array of child nodes

Pipe chains

Pipe expressions are parsed as a single PipeNode containing the source, transform steps, and optional render sink:

// @list ./src/ | grep \.ts$ | sort | @render type="numbered"
// PipeNode {
//   source: ListNode { path: './src/' },
//   steps:  ['grep \\.ts$', 'sort'],
//   sink:   RenderNode { type: 'numbered' }
// }

API Reference

parse(source: string): MarkdownAIDocument

Parses a MarkdownAI document string and returns the AST.

  • source - the full document text
  • Returns a MarkdownAIDocument with header and nodes fields
  • Throws ParseError if a block directive is unclosed or the structure is invalid

ParseError

Thrown when the document has a structural error (unclosed @define, nested @phase in an import, etc.). Has message, line, and directive fields.

import { parse, ParseError } from '@markdownai/parser'

try {
  parse('@markdownai\n@define foo\n# unclosed')
} catch (e) {
  if (e instanceof ParseError) {
    console.error(`Parse error at line ${e.line}: ${e.message}`)
  }
}

scanInterpolations(source: string): string[]

Returns all {{ expression }} expressions found in a source string. Useful for static analysis without a full parse.

import { scanInterpolations } from '@markdownai/parser'

const exprs = scanInterpolations('Hello {{ name }}, today is {{ date format="YYYY-MM-DD" }}')
// ["name", 'date format="YYYY-MM-DD"']

scanShellInlines(source: string): string[]

Returns all !`command` shell inline expressions found in a source string.

import { scanShellInlines } from '@markdownai/parser'

const cmds = scanShellInlines('Branch: !`git branch --show-current`')
// ["git branch --show-current"]

What the parser does NOT do

  • Execute any directive
  • Read any file
  • Make any network request
  • Access environment variables
  • Resolve macros or evaluate expressions

All of that happens in @markdownai/engine.

TypeScript

The parser is written in strict TypeScript and ships with full type declarations. All AST node types are exported and usable directly:

import type { MarkdownAIDocument, DefineNode, ConditionalNode, PipeNode } from '@markdownai/parser'

function findMacros(doc: MarkdownAIDocument): DefineNode[] {
  return doc.nodes.filter((n): n is DefineNode => n.type === 'DefineNode')
}

Part of the MarkdownAI toolchain

The parser is the first step in the pipeline. To go further:

License

MIT - GitHub