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.
- package/README.md +91 -47
- package/dist/cli.mjs +145 -13
- 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
|
|
70
|
+
mdsel README.md
|
|
20
71
|
|
|
21
72
|
# Select a specific heading (shorthand)
|
|
22
|
-
mdsel
|
|
73
|
+
mdsel README.md h1.0
|
|
23
74
|
|
|
24
75
|
# Select the first code block under a heading
|
|
25
|
-
mdsel
|
|
76
|
+
mdsel README.md "h2.0/code.0"
|
|
26
77
|
|
|
27
78
|
# Select multiple headings with comma syntax
|
|
28
|
-
mdsel
|
|
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
|
|
85
|
+
mdsel README.md "h2.0?head=10"
|
|
32
86
|
|
|
33
87
|
# Limit output to last 5 lines
|
|
34
|
-
mdsel
|
|
88
|
+
mdsel README.md "h2.0?tail=5"
|
|
35
89
|
|
|
36
90
|
# Use JSON output for programmatic consumption
|
|
37
|
-
mdsel
|
|
91
|
+
mdsel --json README.md
|
|
38
92
|
```
|
|
39
93
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
### index
|
|
94
|
+
## Usage
|
|
43
95
|
|
|
44
|
-
|
|
96
|
+
mdsel automatically detects whether arguments are files or selectors:
|
|
45
97
|
|
|
46
98
|
```bash
|
|
47
|
-
mdsel
|
|
48
|
-
mdsel
|
|
99
|
+
mdsel <files...> [selectors...]
|
|
100
|
+
mdsel [options] <files...> [selectors...]
|
|
49
101
|
```
|
|
50
102
|
|
|
51
|
-
**
|
|
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
|
|
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
|
|
62
|
-
h3.0
|
|
63
|
-
h3.1
|
|
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
|
-
###
|
|
158
|
+
### Select (files + selectors)
|
|
99
159
|
|
|
100
|
-
|
|
160
|
+
When both files and selectors are provided, mdsel retrieves matching content:
|
|
101
161
|
|
|
102
162
|
```bash
|
|
103
|
-
|
|
104
|
-
mdsel
|
|
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
|
-
|
|
115
|
-
|
|
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
|
|
123
|
-
mdsel
|
|
169
|
+
# Cross-document selection
|
|
170
|
+
mdsel README.md GUIDE.md h1.0
|
|
124
171
|
|
|
125
|
-
#
|
|
126
|
-
mdsel
|
|
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
|
|
176
|
+
mdsel README.md h2.1-3
|
|
133
177
|
|
|
134
|
-
#
|
|
135
|
-
mdsel
|
|
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
|
|
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
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|