mdsel 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +91 -47
  2. package/dist/cli.mjs +145 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,6 +4,57 @@ Declarative Markdown semantic selection CLI for LLM agents.
4
4
 
5
5
  mdsel parses Markdown documents into semantic trees and exposes machine-addressable selectors for every meaningful chunk. It enables LLMs to request exactly the content they want—no more, no less—without loading entire files into context.
6
6
 
7
+ ## Demo
8
+
9
+ **1. Index a document to see its structure:**
10
+
11
+ ```bash
12
+ $ mdsel README.md
13
+ h1 mdsel
14
+ h2 Demo
15
+ h2 Installation
16
+ h2 Quick Start
17
+ h2 Commands
18
+ h3 index
19
+ h3 select
20
+ h2 Selectors
21
+ h3 Syntax
22
+ h3 Node Types
23
+ h3 Index Syntax
24
+ h3 Examples
25
+ h3 Index Semantics
26
+ h2 Output Format
27
+ h2 Error Handling
28
+ h2 Development
29
+ h2 License
30
+ ---
31
+ code:19 para:23 list:5 table:3
32
+ ```
33
+
34
+ **2. Select specific content by selector:**
35
+
36
+ ```bash
37
+ $ mdsel README.md h2.1
38
+ ```
39
+ ```
40
+ ## Installation
41
+
42
+ ​```bash
43
+ npm install -g mdsel
44
+ ​```
45
+
46
+ **Requirements**: Node.js >=18.0.0
47
+ ```
48
+
49
+ **3. Drill into nested content:**
50
+
51
+ ```bash
52
+ $ mdsel README.md h2.1/code.0
53
+ ```
54
+ ```
55
+ npm install -g mdsel
56
+ ```
57
+
7
58
  ## Installation
8
59
 
9
60
  ```bash
@@ -16,41 +67,50 @@ npm install -g mdsel
16
67
 
17
68
  ```bash
18
69
  # Index a document to discover available selectors
19
- mdsel index README.md
70
+ mdsel README.md
20
71
 
21
72
  # Select a specific heading (shorthand)
22
- mdsel select h1.0 README.md
73
+ mdsel README.md h1.0
23
74
 
24
75
  # Select the first code block under a heading
25
- mdsel select "h2.0/code.0" README.md
76
+ mdsel README.md "h2.0/code.0"
26
77
 
27
78
  # Select multiple headings with comma syntax
28
- mdsel select h2.0,2 README.md
79
+ mdsel README.md h2.0,2
80
+
81
+ # Select multiple selectors at once
82
+ mdsel README.md h2.0 h2.1 code.0
29
83
 
30
84
  # Limit output to first 10 lines
31
- mdsel select "h2.0?head=10" README.md
85
+ mdsel README.md "h2.0?head=10"
32
86
 
33
87
  # Limit output to last 5 lines
34
- mdsel select "h2.0?tail=5" README.md
88
+ mdsel README.md "h2.0?tail=5"
35
89
 
36
90
  # Use JSON output for programmatic consumption
37
- mdsel index README.md --json
91
+ mdsel --json README.md
38
92
  ```
39
93
 
40
- ## Commands
41
-
42
- ### index
94
+ ## Usage
43
95
 
44
- Parse documents and emit selector inventory.
96
+ mdsel automatically detects whether arguments are files or selectors:
45
97
 
46
98
  ```bash
47
- mdsel index <files...>
48
- mdsel index <files...> --json
99
+ mdsel <files...> [selectors...]
100
+ mdsel [options] <files...> [selectors...]
49
101
  ```
50
102
 
51
- **Example**:
103
+ **Options**:
104
+ - `--json` - Output JSON instead of text
105
+
106
+ ### Index (files only)
107
+
108
+ When only files are provided, mdsel outputs the document structure:
109
+
52
110
  ```bash
53
- mdsel index README.md docs/API.md
111
+ mdsel README.md
112
+ mdsel README.md docs/API.md
113
+ mdsel --json README.md
54
114
  ```
55
115
 
56
116
  **Text Output** (default):
@@ -58,9 +118,9 @@ mdsel index README.md docs/API.md
58
118
  h1.0 mdsel
59
119
  h2.0 Installation
60
120
  h2.1 Quick Start
61
- h2.2 Commands
62
- h3.0 index
63
- h3.1 select
121
+ h2.2 Usage
122
+ h3.0 Index
123
+ h3.1 Select
64
124
  ---
65
125
  code:19 para:23 list:5 table:3
66
126
  ```
@@ -95,44 +155,28 @@ code:19 para:23 list:5 table:3
95
155
  }
96
156
  ```
97
157
 
98
- ### select
158
+ ### Select (files + selectors)
99
159
 
100
- Retrieve content via selectors.
160
+ When both files and selectors are provided, mdsel retrieves matching content:
101
161
 
102
162
  ```bash
103
- mdsel select <selector> [files...]
104
- mdsel select <selector> [files...] --json
105
- ```
106
-
107
- **Arguments**:
108
- - `<selector>` - Selector string (see [Selectors](#selectors))
109
- - `[files...]` - Markdown files to search (optional, uses stdin if omitted)
110
-
111
- **Options**:
112
- - `--json` - Output JSON instead of text
163
+ # Single selector
164
+ mdsel README.md h2.0
113
165
 
114
- **Examples**:
115
- ```bash
116
- # Select first h2 (shorthand)
117
- mdsel select h2.0 README.md
118
-
119
- # Select first code block
120
- mdsel select code.0 README.md
166
+ # Multiple selectors
167
+ mdsel README.md h2.0 h2.1 code.0
121
168
 
122
- # Cross-document selection (all documents)
123
- mdsel select h1.0 README.md GUIDE.md
169
+ # Cross-document selection
170
+ mdsel README.md GUIDE.md h1.0
124
171
 
125
- # Limit output to first 10 lines
126
- mdsel select "h2.0?head=10" README.md
127
-
128
- # Limit output to last 5 lines
129
- mdsel select "h2.0?tail=5" README.md
172
+ # With query parameters
173
+ mdsel README.md "h2.0?head=10"
130
174
 
131
175
  # Range selection
132
- mdsel select h2.1-3 README.md
176
+ mdsel README.md h2.1-3
133
177
 
134
- # Multiple specific indices
135
- mdsel select h2.0,2,4 README.md
178
+ # Comma list selection
179
+ mdsel README.md h2.0,2,4
136
180
  ```
137
181
 
138
182
  **Text Output** (default):
package/dist/cli.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/cli/index.ts
4
4
  import { Command } from "commander";
5
5
  import { createRequire } from "module";
6
+ import { existsSync } from "fs";
6
7
 
7
8
  // src/parser/parse.ts
8
9
  import { readFile, access, stat } from "fs/promises";
@@ -548,7 +549,7 @@ function countBlock(node, counts) {
548
549
 
549
550
  // src/cli/utils/file-reader.ts
550
551
  function isStdinPiped() {
551
- return !process.stdin.isTTY;
552
+ return process.stdin.isTTY === false;
552
553
  }
553
554
  function readStdin() {
554
555
  return new Promise((resolve, reject) => {
@@ -1846,6 +1847,104 @@ function formatMatches(results, truncateOpts) {
1846
1847
  };
1847
1848
  });
1848
1849
  }
1850
+ async function selectMultiCommand(selectors, files, options = {}) {
1851
+ const useJson = options.json === true;
1852
+ if (files.length === 0) {
1853
+ const error = createErrorEntry(
1854
+ "PARSE_ERROR",
1855
+ "NO_FILES",
1856
+ "No files provided. Specify files to search."
1857
+ );
1858
+ outputError2([error], useJson);
1859
+ exitWithCode(ExitCode.ERROR);
1860
+ return;
1861
+ }
1862
+ const documents = [];
1863
+ const parseErrors = [];
1864
+ for (const file of files) {
1865
+ try {
1866
+ const result = await parseFile(file);
1867
+ const namespace = deriveNamespace(file);
1868
+ const availableSelectors = buildAvailableSelectors(result.ast, namespace);
1869
+ documents.push({
1870
+ namespace,
1871
+ tree: result.ast,
1872
+ availableSelectors
1873
+ });
1874
+ } catch (error) {
1875
+ if (error instanceof ParserError) {
1876
+ parseErrors.push(
1877
+ createErrorEntry(
1878
+ error.code,
1879
+ error.code,
1880
+ error.message,
1881
+ error.filePath
1882
+ )
1883
+ );
1884
+ } else if (error instanceof Error) {
1885
+ parseErrors.push(createErrorEntry("PROCESSING_ERROR", "UNKNOWN", error.message, file));
1886
+ }
1887
+ }
1888
+ }
1889
+ if (documents.length === 0) {
1890
+ outputError2(parseErrors, useJson);
1891
+ exitWithCode(ExitCode.ERROR);
1892
+ return;
1893
+ }
1894
+ const allMatches = [];
1895
+ const allUnresolved = [];
1896
+ for (const selector of selectors) {
1897
+ let selectorAst;
1898
+ try {
1899
+ selectorAst = parseSelector(selector);
1900
+ } catch (error) {
1901
+ if (error instanceof SelectorParseError) {
1902
+ allUnresolved.push({
1903
+ selector,
1904
+ reason: error.message,
1905
+ suggestions: []
1906
+ });
1907
+ continue;
1908
+ }
1909
+ throw error;
1910
+ }
1911
+ const truncateOptions = {};
1912
+ if (selectorAst.queryParams) {
1913
+ for (const param of selectorAst.queryParams) {
1914
+ if (param.key === "head") {
1915
+ const value = parseInt(param.value, 10);
1916
+ if (!isNaN(value) && value > 0) {
1917
+ truncateOptions.head = value;
1918
+ }
1919
+ } else if (param.key === "tail") {
1920
+ const value = parseInt(param.value, 10);
1921
+ if (!isNaN(value) && value > 0) {
1922
+ truncateOptions.tail = value;
1923
+ }
1924
+ }
1925
+ }
1926
+ }
1927
+ const outcome = resolveMulti(documents, selectorAst);
1928
+ if (outcome.success) {
1929
+ const matches = formatMatches(outcome.results, truncateOptions);
1930
+ allMatches.push(...matches);
1931
+ } else {
1932
+ const err = outcome.error;
1933
+ allUnresolved.push({
1934
+ selector: err.selector,
1935
+ reason: err.message,
1936
+ suggestions: err.suggestions.map((s) => s.selector)
1937
+ });
1938
+ }
1939
+ }
1940
+ if (useJson) {
1941
+ const response = formatSelectResponse(allMatches, allUnresolved);
1942
+ console.log(JSON.stringify(response));
1943
+ } else {
1944
+ console.log(formatSelectText(allMatches, allUnresolved));
1945
+ }
1946
+ exitWithCode(allUnresolved.length > 0 ? ExitCode.ERROR : ExitCode.SUCCESS);
1947
+ }
1849
1948
 
1850
1949
  // src/cli/commands/format-command.ts
1851
1950
  function formatCommand(command, options = {}) {
@@ -1866,21 +1965,54 @@ function formatCommand(command, options = {}) {
1866
1965
  // src/cli/index.ts
1867
1966
  var require2 = createRequire(import.meta.url);
1868
1967
  var pkg = require2("../package.json");
1869
- var program = new Command();
1870
- program.name("mdsel").description(pkg.description).version(pkg.version).option("--json", "Output JSON instead of minimal text");
1871
- program.command("index", { isDefault: true }).description("Parse documents and emit selector inventory").argument("<files...>", "Markdown files to index").action(async (files) => {
1872
- try {
1873
- const globalOpts = program.opts();
1874
- await indexCommand(files, { json: globalOpts.json });
1875
- } catch (error) {
1876
- console.error("Unexpected error:", error);
1877
- process.exit(ExitCode.ERROR);
1968
+ function isFilePath(arg) {
1969
+ if (/\.(md|markdown)$/i.test(arg)) {
1970
+ return true;
1878
1971
  }
1879
- });
1880
- program.command("select").description("Retrieve content via selectors").argument("<selector>", "Selector string").argument("[files...]", "Markdown files to search").action(async (selector, files) => {
1972
+ if (existsSync(arg)) {
1973
+ return true;
1974
+ }
1975
+ return false;
1976
+ }
1977
+ function partitionArgs(args) {
1978
+ const files = [];
1979
+ const selectors = [];
1980
+ for (const arg of args) {
1981
+ if (isFilePath(arg)) {
1982
+ files.push(arg);
1983
+ } else {
1984
+ selectors.push(arg);
1985
+ }
1986
+ }
1987
+ return { files, selectors };
1988
+ }
1989
+ var program = new Command();
1990
+ program.name("mdsel").description(pkg.description).version(pkg.version).option("--json", "Output JSON instead of minimal text").argument("[args...]", "Files and/or selectors").action(async (args) => {
1881
1991
  try {
1882
1992
  const globalOpts = program.opts();
1883
- await selectCommand(selector, files, { json: globalOpts.json });
1993
+ if (args.length === 0) {
1994
+ if (isStdinPiped()) {
1995
+ await indexCommand([], { json: globalOpts.json });
1996
+ return;
1997
+ }
1998
+ program.outputHelp();
1999
+ process.exit(0);
2000
+ }
2001
+ const { files, selectors } = partitionArgs(args);
2002
+ if (files.length === 0) {
2003
+ console.error("Error: No markdown files provided.");
2004
+ console.error("Usage: mdsel <file.md> [selector...]");
2005
+ process.exit(ExitCode.ERROR);
2006
+ }
2007
+ if (selectors.length === 0) {
2008
+ await indexCommand(files, { json: globalOpts.json });
2009
+ return;
2010
+ }
2011
+ if (selectors.length === 1) {
2012
+ await selectCommand(selectors[0], files, { json: globalOpts.json });
2013
+ } else {
2014
+ await selectMultiCommand(selectors, files, { json: globalOpts.json });
2015
+ }
1884
2016
  } catch (error) {
1885
2017
  console.error("Unexpected error:", error);
1886
2018
  process.exit(ExitCode.ERROR);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdsel",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Declarative Markdown semantic selection CLI for LLM agents",
5
5
  "type": "module",
6
6
  "bin": {