mdq-cli 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +335 -114
  2. package/bin/mdq.js +3 -38
  3. package/index.js +7 -7
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,187 +1,408 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/testomatio/explorbot/main/assets/logos/mdq/mdq-icon-bg-v2.png" alt="mdq" width="160">
3
+ </p>
4
+
1
5
  # mdq
2
6
 
3
- Query and edit markdown with a selector language — jq, for markdown.
7
+ Query, validate, extract, and update structured Markdown with a selector language. Like `jq` for Markdown.
4
8
 
5
- ## Install
9
+ ## Usage examples
10
+
11
+ ```bash
12
+ npx mdq-cli 'section("Overview")' generated.md # validate
13
+ npx mdq-cli 'section("API") table' --json README.md # extract
14
+ npx mdq-cli 'section("Tasks") list' --add-item 'Review docs' --in-place plan.md # update
15
+ ```
16
+
17
+ ### Validate the structure of LLM-generated Markdown
18
+
19
+ Use a selector as a structural assertion. The command prints the matching Markdown and exits with status `0` when the section exists, `1` when it does not exist, or `2` when the selector or input is invalid:
6
20
 
7
21
  ```bash
8
- npx mdq-cli 'h2' README.md # no install
9
- npm install mdq-cli # as a library
10
- npm install -g mdq-cli # as a command
22
+ npx mdq-cli 'section("Overview")' generated.md
11
23
  ```
12
24
 
13
- Node 18 or newer. Two dependencies: `marked` and `yaml`.
25
+ Validate more specific structure by composing selectors:
14
26
 
15
- ## Use
27
+ ```bash
28
+ npx mdq-cli 'section("API") table' generated.md
29
+ npx mdq-cli 'section("Examples") code[0]' generated.md
30
+ ```
31
+
32
+ These queries check for an **Overview** section, a table inside **API**, and at least one fenced code block inside **Examples**. An unknown selector raises an error instead of silently matching nothing.
33
+
34
+ The same validation can be scripted in JavaScript:
16
35
 
17
36
  ```js
37
+ import { readFile } from 'node:fs/promises';
18
38
  import { mdq } from 'mdq-cli';
19
39
 
20
- mdq(readme).query('section("Install") code[0]').text();
21
- mdq(plan).comment(/^test/).nodes();
22
- mdq(doc).table().addRow({ Method: 'POST', Path: '/sessions' }).toString();
40
+ const source = await readFile('generated.md', 'utf8');
41
+ const doc = mdq(source);
42
+
43
+ const valid =
44
+ doc.section('Overview').exists() &&
45
+ doc.query('section("API") table').exists() &&
46
+ doc.query('section("Examples") code[0]').exists();
47
+
48
+ if (!valid) throw new Error('Generated Markdown does not match the required structure');
23
49
  ```
24
50
 
25
- There is a CLI too:
51
+ ### Extract data from structured Markdown
52
+
53
+ Read a table as JSON from the CLI:
26
54
 
27
55
  ```bash
28
56
  npx mdq-cli 'section("API") table' --json README.md
29
57
  ```
30
58
 
31
- ## The one rule
59
+ Or extract typed structures through the JavaScript API:
60
+
61
+ ```js
62
+ import { mdq } from 'mdq-cli';
63
+
64
+ const endpoints = mdq(readme).query('section("API") table').rows();
65
+ const metadata = mdq(plan).section('Metadata').entries();
66
+ const testComments = mdq(doc).comment(/^test/).nodes();
67
+ ```
68
+
69
+ ### Update data in Markdown
70
+
71
+ Append a row to a table and save the file in place:
72
+
73
+ ```bash
74
+ npx mdq-cli 'section("API") table' \
75
+ --add-row '{"Method":"POST","Path":"/sessions"}' \
76
+ --in-place README.md
77
+ ```
78
+
79
+ Or compose several edits in JavaScript:
80
+
81
+ ```js
82
+ import { mdq } from 'mdq-cli';
83
+
84
+ const updated = mdq(source)
85
+ .query('section("API") table')
86
+ .addRow({ Method: 'POST', Path: '/sessions' })
87
+ .query('section("Notes")')
88
+ .append('- Document the new endpoint\n')
89
+ .toString();
90
+ ```
91
+
92
+ ## Install
93
+
94
+ ```bash
95
+ npx mdq-cli 'h2' README.md # run without installing
96
+ npm install mdq-cli # use as a library
97
+ npm install -g mdq-cli # install the mdq-cli command globally
98
+ ```
99
+
100
+ mdq requires Node.js 18 or newer. Its implementation libraries are described in [Implementation](#implementation).
101
+
102
+ ## Use mdq with coding agents
103
+
104
+ Add this instruction to your project's `AGENTS.md`:
105
+
106
+ ```md
107
+ When you need to inspect, validate, or edit structured Markdown, use `npx mdq-cli` selectors instead of parsing or rewriting the document manually; run `npx mdq-cli --help` for the available query and edit options.
108
+ ```
32
109
 
33
- **Reads narrow, writes return the document.**
110
+ This gives an agent a deterministic CLI for checking generated documents, extracting structured content, and making targeted edits without rewriting unrelated Markdown.
34
111
 
35
- `mdq(source)` gives a `MarkdownQuery` — one class, holding the document and the set of
36
- blocks currently selected. `query()` and the sugar methods narrow that set. Every write
37
- returns a fresh `MarkdownQuery` over the edited document, so edits chain and end with
38
- `toString()`:
112
+ ## Core model
113
+
114
+ **Reads narrow the selection; writes return the document.**
115
+
116
+ `mdq(source)` returns a `MarkdownQuery`. It holds the complete document and the blocks currently selected from it. `query()` and the convenience methods narrow that selection. Every write returns a fresh `MarkdownQuery` over the edited document, so edits can be chained and finished with `toString()`:
39
117
 
40
118
  ```js
41
119
  mdq(source)
42
- .query('section("API")').append('## Notes\n')
43
- .query('blockquote[0]').remove()
120
+ .query('section("API")')
121
+ .append('## Notes\n')
122
+ .query('blockquote[0]')
123
+ .remove()
44
124
  .toString();
45
125
  ```
46
126
 
47
- `toString()` is always the whole document; `text()` is the markdown of the current
48
- selection.
127
+ `toString()` always returns the whole document. `text()` returns the raw Markdown for only the current selection.
49
128
 
50
- ## Selectors
129
+ ## CLI reference
51
130
 
52
- | Selector | Matches |
131
+ ```text
132
+ mdq-cli [options] [selector] [file]
133
+ ```
134
+
135
+ Square brackets in `mdq-cli [options] [selector] [file]` mean that an argument is optional; they are documentation notation, not characters to type.
136
+
137
+ - `[selector]` is the query that chooses Markdown blocks.
138
+ - `[file]` is the input file. When it is omitted, mdq reads the document from standard input.
139
+ - `--in-place` is an output option for edits, not a replacement for `[file]`. It tells mdq to overwrite the input file instead of printing the edited document.
140
+
141
+ ```bash
142
+ npx mdq-cli 'h2' README.md # read from a file
143
+ cat README.md | npx mdq-cli 'h2' # read from standard input
144
+ npx mdq-cli 'comment(~"draft")' --remove README.md
145
+ # print the edited document
146
+ npx mdq-cli 'comment(~"draft")' --remove --in-place README.md
147
+ # overwrite README.md
148
+ ```
149
+
150
+ Only one edit option can be used per invocation. `--in-place` requires a file because there is no input file to overwrite when mdq reads from standard input.
151
+
152
+ ### Read options
153
+
154
+ | Option | Result |
155
+ | --- | --- |
156
+ | `-j, --json` | Output selected table rows as JSON |
157
+ | `-c, --count` | Print the number of matches |
158
+ | `-t, --text` | Print unwrapped node text |
159
+ | `--frontmatter` | Print YAML frontmatter as JSON; no selector is required |
160
+
161
+ Without a read option, mdq prints the raw Markdown for the selection.
162
+
163
+ ### Edit options
164
+
165
+ | Option | Effect |
53
166
  | --- | --- |
54
- | `section` | a heading and everything under it, until the next heading of the same or shallower depth |
55
- | `section1` … `section6` | the same, restricted to one heading depth |
56
- | `heading`, `h1` … `h6` | the heading line alone |
57
- | `paragraph` | a paragraph |
58
- | `table` | a GFM table |
59
- | `list` | a bullet or ordered list |
60
- | `item` | one item of a list |
61
- | `code` | a fenced code block |
62
- | `blockquote` | a `>` block |
63
- | `hr` | a thematic break |
64
- | `html` | an HTML block, matched on its raw text |
65
- | `comment` | an HTML comment, matched on its **inner** body |
167
+ | `--remove` | Delete matched blocks |
168
+ | `--replace <markdown>` | Replace matched blocks |
169
+ | `--insert-before <markdown>` | Insert a sibling block before each match |
170
+ | `--insert-after <markdown>` | Insert a sibling block after each match |
171
+ | `--prepend <markdown>` | Insert at the start of each matched section or list |
172
+ | `--append <markdown>` | Insert at the end of each matched section or list |
173
+ | `--add-row <json>` | Append a row to each matched table |
174
+ | `--add-item <text>` | Append an item to each matched list |
175
+ | `--set <key=value>` | Set a `Key: value` entry; omit `=value` to delete the key |
176
+ | `-i, --in-place` | Write the edited document back to the input file |
177
+
178
+ Without `--in-place`, an edit prints the complete edited document to standard output. `--in-place` requires a file.
179
+
180
+ ### Use mdq in pipelines
181
+
182
+ Because mdq reads from standard input when `[file]` is omitted, it can query generated Markdown without creating a temporary file:
183
+
184
+ ```bash
185
+ generate-markdown | npx mdq-cli 'section("Summary")'
186
+ ```
66
187
 
67
- Text matchers go in parentheses, and `!` negates any of them:
188
+ Pipe selected table rows as JSON into another data tool:
68
189
 
190
+ ```bash
191
+ npx mdq-cli 'section("API") table' --json README.md |
192
+ jq -r '.[].Path'
69
193
  ```
70
- section("Install") exact
71
- section(~"Inst") contains
72
- section(/^inst/i) regex, with its own flags
73
- section(!~"Draft") negated
194
+
195
+ Pipe a selected Markdown block to another command:
196
+
197
+ ```bash
198
+ cat README.md |
199
+ npx mdq-cli 'section("Install")' |
200
+ markdownlint
74
201
  ```
75
202
 
76
- Index and slice with brackets, and compose with spaces to scope one selector inside
77
- another:
203
+ Edits without `--in-place` print the complete edited document, so multiple mdq edits can be chained:
78
204
 
205
+ ```bash
206
+ npx mdq-cli 'section("Deprecated")' --remove README.md |
207
+ npx mdq-cli 'section("Notes") list[0]' --add-item 'Reviewed' \
208
+ > README.updated.md
79
209
  ```
80
- heading[0] first
81
- heading[-1] last
210
+
211
+ To replace the original file, prefer `--in-place`. Do not redirect output back to the same file being read, such as `... README.md > README.md`, because the shell truncates the file before mdq can read it.
212
+
213
+ ### Exit status
214
+
215
+ | Status | Meaning |
216
+ | --- | --- |
217
+ | `0` | The query matched, the count completed, or the edit succeeded |
218
+ | `1` | The selector matched nothing |
219
+ | `2` | The command, selector, input file, or operation was invalid |
220
+
221
+ `--count` exits with status `0`, including when the count is zero.
222
+
223
+ ## Selector reference
224
+
225
+ | Selector | Matches |
226
+ | --- | --- |
227
+ | `section` | A heading and everything below it, up to the next heading of the same or shallower depth |
228
+ | `section1` … `section6` | The same section range, restricted to one heading depth |
229
+ | `heading`, `h1` … `h6` | The heading line only |
230
+ | `paragraph` | A paragraph |
231
+ | `table` | A GFM table |
232
+ | `list` | A bullet or ordered list |
233
+ | `item` | One list item |
234
+ | `code` | A fenced code block |
235
+ | `blockquote` | A `>` block |
236
+ | `hr` | A thematic break |
237
+ | `html` | An HTML block, matched against its raw text |
238
+ | `comment` | An HTML comment, matched against its **inner** body |
239
+
240
+ ### Match text
241
+
242
+ Put a text matcher in parentheses. Prefix any matcher with `!` to negate it.
243
+
244
+ ```text
245
+ section("Install") exact match
246
+ section(~"Inst") contains text
247
+ section(/^inst/i) regular expression, with its own flags
248
+ section(!~"Draft") negated match
249
+ ```
250
+
251
+ ### Select by index or slice
252
+
253
+ Use brackets to select by position. Negative indexes count from the end, and slices follow Python-style `from:to` bounds.
254
+
255
+ ```text
256
+ heading[0] first heading
257
+ heading[-1] last heading
82
258
  blockquote[2:5] a slice
83
- section("API") table every table inside that section
84
259
  ```
85
260
 
86
- A leading `.` is accepted and ignored, so `.h2` works if that is your habit from jq.
261
+ ### Scope selectors
87
262
 
88
- An unknown selector throws `MdqSelectorError`, which carries the `index` of the offending
89
- character. It never silently matches nothing.
263
+ Separate selectors with spaces to scope each selector inside the previous one:
90
264
 
91
- ## Matchers as values
265
+ ```text
266
+ section("API") table every table inside the API section
267
+ ```
268
+
269
+ A leading `.` is accepted and ignored, so `.h2` works if that is your habit from `jq`.
270
+
271
+ An unknown selector throws `MdqSelectorError`. The error carries the `index` of the offending character; mdq never silently treats an unknown selector as an empty result.
272
+
273
+ ## Matchers as JavaScript values
92
274
 
93
- Passing a JavaScript value avoids escaping a dynamic string into a selector:
275
+ Pass a JavaScript matcher value when building a selector string would require escaping dynamic input:
94
276
 
95
277
  ```js
96
- mdq(doc).query('section2', section.name); // exact
97
- mdq(doc).heading(/^summary/i); // regex, own flags
278
+ mdq(doc).query('section2', section.name); // exact string
279
+ mdq(doc).heading(/^summary/i); // RegExp with its own flags
98
280
  mdq(doc).item((text) => text.length > 80); // predicate
99
281
  ```
100
282
 
101
- A `string` matches exactly, a `RegExp` honors its own flags, and a function is a predicate
102
- over the node's text. Every sugar method takes one: `section` `heading` `paragraph` `table`
103
- `list` `item` `code` `blockquote` `comment` `html` `hr`. Each is exactly
104
- `query(selector, matcher)`; `section` and `heading` also take `{ depth }`.
283
+ A `string` matches exactly, a `RegExp` honors its own flags, and a function is called as a predicate over the node's text.
284
+
285
+ These convenience selector methods accept a matcher:
286
+
287
+ - `section`
288
+ - `heading`
289
+ - `paragraph`
290
+ - `table`
291
+ - `list`
292
+ - `item`
293
+ - `code`
294
+ - `blockquote`
295
+ - `comment`
296
+ - `html`
297
+
298
+ Each is equivalent to `query(selector, matcher)`. The `hr()` convenience method takes no matcher. `section` and `heading` also accept `{ depth }`:
105
299
 
106
- ## Reading
300
+ ```js
301
+ mdq(doc).section('Install', { depth: 2 });
302
+ mdq(doc).heading(/^summary/i, { depth: 3 });
303
+ ```
304
+
305
+ ## Reading with the JavaScript API
107
306
 
108
307
  | Method | Returns |
109
308
  | --- | --- |
110
- | `text()` | raw markdown of every match, joined |
111
- | `nodes()` | `{ type, depth, text }` per match |
112
- | `rows()` | table rows as objects, keyed by header |
113
- | `entries()` | `Key: value` lines of a block, keys lowercased |
114
- | `count()` / `exists()` | how many matched / whether any did |
115
- | `first()` / `last()` / `at(n)` / `slice(from, to)` | narrow the selection |
116
- | `each()` | one single-match query per match |
117
- | `preceding()` / `following()` | everything before the first / after the last match |
309
+ | `text()` | Raw Markdown from every match, joined together |
310
+ | `nodes()` | One `{ type, depth, text }` object per match |
311
+ | `rows()` | Table rows as objects keyed by table header |
312
+ | `entries()` | `Key: value` lines from a block, with lowercase keys |
313
+ | `count()` / `exists()` | The number of matches / whether any match exists |
314
+ | `first()` / `last()` / `at(n)` / `slice(from, to)` | A narrowed selection |
315
+ | `each()` | One single-match `MarkdownQuery` per match |
316
+ | `preceding()` / `following()` | Everything before the first / after the last match |
118
317
 
119
- ## Writing
318
+ ## Writing with the JavaScript API
120
319
 
121
- Every one returns a `MarkdownQuery` over the edited document.
320
+ Every write returns a new `MarkdownQuery` over the complete edited document.
122
321
 
123
322
  | Method | Effect |
124
323
  | --- | --- |
125
- | `replace(md)` | replace each match |
126
- | `replaceEach(fn)` | replace each match with `fn(selection, index)` |
127
- | `remove()` | delete each match, and its blank line |
128
- | `insertBefore(md)` / `insertAfter(md)` | add a sibling block |
129
- | `prepend(md)` / `append(md)` | add a block inside a section or list |
130
- | `addRow(obj)` | append a table row, re-aligning the columns |
131
- | `addItem(text)` | append a list item, copying the existing marker |
132
- | `setEntry(key, value)` | set a `Key: value` line; `null` deletes it |
133
-
134
- `prepend` and `append` need a section or list; on any other block they raise
135
- `MdqOperationError`. Anything that takes markdown also takes a `MarkdownQuery`. Writes
136
- never leave zero blank lines between blocks, and never more than one — including inside
137
- fenced code blocks, which are left exactly as they are.
324
+ | `replace(md)` | Replace every match |
325
+ | `replaceEach(fn)` | Replace each match with `fn(selection, index)` |
326
+ | `remove()` | Delete every match and its blank line |
327
+ | `insertBefore(md)` / `insertAfter(md)` | Add a sibling block |
328
+ | `prepend(md)` / `append(md)` | Add a block inside a section or list |
329
+ | `addRow(obj)` | Append a table row and realign the columns |
330
+ | `addItem(text)` | Append a list item using the existing list marker |
331
+ | `setEntry(key, value)` | Set a `Key: value` line; pass `null` to delete it |
332
+ | `setFrontmatter(key, value)` | Set a YAML frontmatter value |
333
+
334
+ `prepend` and `append` require a section or list. Using either method on another block raises `MdqOperationError`.
335
+
336
+ Any method that accepts Markdown also accepts another `MarkdownQuery`. Writes normalize spacing to exactly one blank line between blocks. Content inside fenced code blocks is preserved exactly.
138
337
 
139
338
  ## Frontmatter
140
339
 
141
- A leading `---` block is parsed as YAML, kept out of the token index, and exposed as data.
142
- Without this, `marked` reads `url: /login` as a setext heading.
340
+ A leading `---` block is parsed as YAML, excluded from the token index, and exposed as data. This prevents `marked` from interpreting a line such as `url: /login` as a setext heading.
143
341
 
144
342
  ```js
145
343
  const doc = mdq(page);
146
- doc.frontmatter(); // { url: '/login', wait: 1000, tags: ['auth'] }
147
- doc.query('h2').count(); // 0 — the --- block is not a heading
148
- doc.setFrontmatter('wait', 2000); // comments and formatting survive
344
+
345
+ doc.frontmatter(); // { url: '/login', wait: 1000, tags: ['auth'] }
346
+ doc.query('h2').count(); // 0 — the --- block is not a heading
347
+
348
+ const updated = doc
349
+ .setFrontmatter('wait', 2000) // comments and formatting survive
350
+ .toString();
149
351
  ```
150
352
 
151
- ## Prior art
353
+ From the CLI:
354
+
355
+ ```bash
356
+ npx mdq-cli --frontmatter page.md
357
+ ```
358
+
359
+ ## Implementation
360
+
361
+ Under the hood, mdq uses `marked` to parse Markdown into a block-token AST before it queries or edits anything. Headings, paragraphs, tables, lists, list items, fenced code blocks, blockquotes, HTML, and thematic breaks all come from that parsed structure.
362
+
363
+ ### Processing model
152
364
 
153
- The selector grammar here is **bespoke**. It is not a standard, and there is no upstream
154
- parser for it — element names and descendant-by-space come from CSS, `[2:5]` slices from
155
- Python, and a tolerated leading `.` from jq.
365
+ 1. A leading YAML frontmatter block is separated from the Markdown body.
366
+ 2. `marked.lexer()` parses the body into block tokens, including nested list items and GFM table data.
367
+ 3. mdq indexes each token with its range in the original source.
368
+ 4. The selector parser converts the query into validated selector segments.
369
+ 5. The evaluator matches those segments against parsed tokens. It derives sections from heading depth and descendant scope from the selected token ranges.
370
+ 6. Edits splice only the selected source ranges. Untouched text remains unchanged, and the result is parsed again into a fresh `MarkdownQuery`.
156
371
 
157
- The established alternative is the **unified/remark** stack: parse to
158
- [mdast](https://github.com/syntax-tree/mdast), then select with
159
- [`unist-util-select`](https://github.com/syntax-tree/unist-util-select), which implements
160
- real CSS selectors on top of `css-selector-parser`. If you want a standards-based tool,
161
- use that.
372
+ This model provides consistent queries and targeted edits while preserving content outside the selection.
162
373
 
163
- mdq exists because three things do not fall out of that stack:
374
+ ### Selector grammar
164
375
 
165
- - **Sections.** mdast is flat: a heading and the blocks beneath it are siblings, so the CSS
166
- descendant combinator cannot express "this heading and everything under it until the next
167
- heading of the same depth". [`remark-sectionize`](https://github.com/jake-low/remark-sectionize)
168
- adds the nesting, but its synthetic `section` nodes carry no `position`, so their source
169
- range has to be derived from their children before anything can be edited in place.
170
- - **Editing by byte range.** mdq records each block's offset in the original source and
171
- splices text, so anything it does not touch stays byte-identical. mdast nodes do carry
172
- offsets, so this is achievable there too — it is a thing to build, not a thing you get.
173
- - **Text and pattern matching.** mdast headings have no flat text field (the text is a child
174
- node), CSS dropped `:contains()`, and `unist-util-select` parses the attribute `i` flag
175
- but does not apply it — so `/^summary/i` has no selector form at all.
376
+ The selector language has a small, explicit grammar:
176
377
 
177
- GFM tables are not in core remark either; they need `remark-gfm`.
378
+ ```text
379
+ query := segment (whitespace segment)*
380
+ segment := ["." ] name [matcher] position*
381
+ name := section | section1..section6
382
+ | heading | h1..h6
383
+ | paragraph | table | list | item | code
384
+ | blockquote | hr | html | comment
385
+ matcher := "(" ["!"] (quoted | "~" quoted | regex) ")"
386
+ quoted := '"' text '"'
387
+ regex := "/" pattern "/" flags
388
+ position := "[" integer "]"
389
+ | "[" [integer] ":" [integer] "]"
390
+ ```
391
+
392
+ Whitespace between segments means “select inside the previous match.” A leading `.` is accepted as optional syntax. Indexes may be negative, and slices use `from:to` bounds. The complete selector must match this grammar: unknown names and unexpected characters raise `MdqSelectorError` with the failing character position.
393
+
394
+ Markdown structure is not parsed with regular expressions. Regex is limited to selector tokenization, explicit `/pattern/flags` matchers, and small local formatting operations.
395
+
396
+ ### Libraries
397
+
398
+ | Library | Purpose |
399
+ | --- | --- |
400
+ | [`marked`](https://marked.js.org/) | Parse Markdown into block tokens and provide GFM table and nested-list structure |
401
+ | [`yaml`](https://eemeli.org/yaml/) | Parse and update YAML frontmatter through a document model that preserves comments and formatting |
402
+ | [`commander`](https://github.com/tj/commander.js) | Parse CLI arguments and options, and generate command help |
178
403
 
404
+ File input and in-place writes use Node.js built-in filesystem APIs.
179
405
 
180
- ## Limitations
406
+ ## License
181
407
 
182
- - **Block-level comments only.** A comment inside a paragraph (`text <!-- x --> more`) is
183
- part of that paragraph's token and is not reachable as a `comment`.
184
- - **No row or item selectors.** `addRow` and `addItem` append; there is no `removeRow`,
185
- because there is nothing to select.
186
- - **YAML frontmatter only.** TOML (`+++`) and JSON blocks are skipped from the token index
187
- but not parsed.
408
+ MIT
package/bin/mdq.js CHANGED
@@ -3,35 +3,6 @@
3
3
  // src/utils/mdq/cli.ts
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  import { Command } from "commander";
6
- // src/utils/mdq/package.json
7
- var package_default = {
8
- name: "mdq-cli",
9
- version: "0.1.1",
10
- description: "Query and edit markdown with a selector language - jq, for markdown",
11
- license: "MIT",
12
- type: "module",
13
- main: "./index.js",
14
- types: "./types/query.d.ts",
15
- exports: {
16
- ".": {
17
- types: "./types/query.d.ts",
18
- import: "./index.js"
19
- }
20
- },
21
- bin: {
22
- mdq: "./bin/mdq.js"
23
- },
24
- files: ["index.js", "types/", "bin/", "README.md"],
25
- engines: {
26
- node: ">=18"
27
- },
28
- keywords: ["markdown", "query", "selector", "cli", "jq", "frontmatter", "marked", "edit"],
29
- repository: {
30
- type: "git",
31
- url: "https://github.com/testomatio/explorbot",
32
- directory: "src/utils/mdq"
33
- }
34
- };
35
6
 
36
7
  // src/utils/mdq/query.ts
37
8
  import { marked } from "marked";
@@ -662,19 +633,13 @@ var SEGMENT = /(\s*\.?)([A-Za-z]\w*)(?:\((!?)(~?)(?:"((?:[^"\\]|\\.)*)"|\/((?:[^
662
633
  // src/utils/mdq/cli.ts
663
634
  var EDIT_FLAGS = ["remove", "replace", "insertBefore", "insertAfter", "prepend", "append", "addRow", "addItem", "set"];
664
635
  async function runMdq(argv, readStdin) {
665
- let printed = "";
666
636
  const program = new Command;
667
- program.name("mdq").description("query and edit markdown").version(package_default.version, "-v, --version").argument("[selector]", "markdown selector").argument("[file]", "file to read; stdin when omitted").option("-j, --json", "output rows as JSON").option("-c, --count", "print the number of matches").option("-t, --text", "print unwrapped text").option("--frontmatter", "print frontmatter as JSON").option("-i, --in-place", "write the result back to the file").option("--remove", "delete matched blocks").option("--replace <markdown>", "replace matched blocks").option("--insert-before <markdown>", "insert before each match").option("--insert-after <markdown>", "insert after each match").option("--prepend <markdown>", "insert at the start of each match").option("--append <markdown>", "insert at the end of each match").option("--add-row <json>", "append a table row").option("--add-item <text>", "append a list item").option("--set <key=value>", "set an entry; omit the value to delete it").exitOverride().configureOutput({
668
- writeOut: (text) => {
669
- printed += text;
670
- },
671
- writeErr: () => {}
672
- });
637
+ program.name("mdq-cli").description("query and edit markdown").argument("[selector]", "markdown selector").argument("[file]", "file to read; stdin when omitted").option("-j, --json", "output rows as JSON").option("-c, --count", "print the number of matches").option("-t, --text", "print unwrapped text").option("--frontmatter", "print frontmatter as JSON").option("-i, --in-place", "write the result back to the file").option("--remove", "delete matched blocks").option("--replace <markdown>", "replace matched blocks").option("--insert-before <markdown>", "insert before each match").option("--insert-after <markdown>", "insert after each match").option("--prepend <markdown>", "insert at the start of each match").option("--append <markdown>", "insert at the end of each match").option("--add-row <json>", "append a table row").option("--add-item <text>", "append a list item").option("--set <key=value>", "set an entry; omit the value to delete it").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} });
673
638
  try {
674
639
  program.parse(argv, { from: "user" });
675
640
  } catch (error) {
676
- if (printed)
677
- return { output: printed, code: 0 };
641
+ if (error.code === "commander.helpDisplayed")
642
+ return { output: program.helpInformation(), code: 0 };
678
643
  return { output: String(error.message), code: 2 };
679
644
  }
680
645
  const options = program.opts();
package/index.js CHANGED
@@ -624,12 +624,12 @@ var SELECTOR_NAMES = new Set(["section", "heading", "paragraph", "table", "list"
624
624
  var TOKEN_ALIASES = { item: "list_item" };
625
625
  var SEGMENT = /(\s*\.?)([A-Za-z]\w*)(?:\((!?)(~?)(?:"((?:[^"\\]|\\.)*)"|\/((?:[^/\\]|\\.)*)\/([a-z]*))\))?((?:\[[^\]]*\])*)\s*/y;
626
626
  export {
627
- parseQuery,
628
- mdq,
629
- buildTokenIndex,
630
- MdqSelectorError,
631
- MdqOperationError,
632
- MdqError,
627
+ MarkdownEditor,
633
628
  MarkdownQuery,
634
- MarkdownEditor
629
+ MdqError,
630
+ MdqOperationError,
631
+ MdqSelectorError,
632
+ buildTokenIndex,
633
+ mdq,
634
+ parseQuery
635
635
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdq-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Query and edit markdown with a selector language - jq, for markdown",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "bin": {
16
- "mdq": "./bin/mdq.js"
16
+ "mdq-cli": "./bin/mdq.js"
17
17
  },
18
18
  "files": [
19
19
  "index.js",