-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.ts
More file actions
374 lines (343 loc) · 17.2 KB
/
cli.ts
File metadata and controls
374 lines (343 loc) · 17.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#!/usr/bin/env node
import { Command } from 'commander';
import { convertCommand } from './commands/convert.js';
import { indexCommand } from './commands/index.js';
import { joinCommand } from './commands/join.js';
import { mergeCommand } from './commands/merge.js';
import { moveCommand } from './commands/move.js';
import { splitCommand } from './commands/split.js';
import { tocCommand } from './commands/toc.js';
import { validateCommand } from './commands/validate.js';
import { checkLinksCommand } from './commands/check-links.js';
const program = new Command();
program
.name('markmv')
.description('CLI for markdown file operations with intelligent link refactoring')
.version('0.1.0');
program
.command('convert')
.description('Convert markdown link formats and path resolution')
.argument('<files...>', 'Markdown files to convert (supports globs like *.md, **/*.md)')
.option('--path-resolution <type>', 'Convert path resolution: absolute|relative')
.option(
'--base-path <path>',
'Base path for relative path calculations (defaults to current directory)'
)
.option('--link-style <style>', 'Convert link style: markdown|claude|combined|wikilink')
.option('-r, --recursive', 'Process directories recursively')
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output with processing information')
.option('--json', 'Output results in JSON format')
.addHelpText(
'after',
`
Examples:
$ markmv convert docs/*.md --link-style wikilink --path-resolution relative
$ markmv convert README.md --link-style claude --dry-run
$ markmv convert **/*.md --path-resolution absolute --recursive
$ markmv convert docs/ --link-style combined --recursive --verbose
Link Styles:
markdown Standard markdown links: [text](url)
claude Claude import syntax: @url
combined Combined format: [@url](url)
wikilink Obsidian wikilinks: [[url]]
Path Resolution:
absolute Convert to absolute file paths
relative Convert to relative file paths from base-path`
)
.action(convertCommand);
program
.command('move')
.description('Move markdown files while updating cross-references')
.argument(
'<sources...>',
'Source markdown files and destination (supports globs like *.md, **/*.md)'
)
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.action(moveCommand);
program
.command('split')
.description('Split large markdown files maintaining link integrity')
.argument('<source>', 'Source markdown file to split')
.option('-s, --strategy <strategy>', 'Split strategy: headers|size|manual|lines', 'headers')
.option('-o, --output <dir>', 'Output directory for split files')
.option('-l, --header-level <level>', 'Header level to split on (1-6)', '2')
.option('-m, --max-size <kb>', 'Maximum size per section in KB', '100')
.option('--split-lines <lines>', 'Comma-separated line numbers to split on (for lines strategy)')
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.action(splitCommand);
program
.command('join')
.description('Join multiple markdown files resolving conflicts')
.argument('<files...>', 'Markdown files to join')
.option('-o, --output <file>', 'Output file name')
.option(
'--order-strategy <strategy>',
'Order strategy: alphabetical|manual|dependency|chronological',
'dependency'
)
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.action(joinCommand);
program
.command('merge')
.description('Merge markdown content with link reconciliation')
.argument('<source>', 'Source markdown file')
.argument('<target>', 'Target markdown file to merge into')
.option('-s, --strategy <strategy>', 'Merge strategy: append|prepend|interactive', 'interactive')
.option('--create-transclusions', 'Create Obsidian transclusions instead of copying content')
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.action(mergeCommand);
program
.command('index')
.description('Generate index files for markdown documentation')
.argument('[directory]', 'Directory to generate indexes for', '.')
.option('-t, --type <type>', 'Index type: links|import|embed|hybrid', 'links')
.option(
'-s, --strategy <strategy>',
'Organization strategy: directory|metadata|manual',
'directory'
)
.option('-l, --location <location>', 'Index placement: all|root|branch|existing', 'root')
.option('-n, --name <name>', 'Index filename', 'index.md')
.option('--embed-style <style>', 'Embed style for embed type: obsidian|markdown', 'obsidian')
.option('--template <file>', 'Custom template file')
.option('--max-depth <number>', 'Maximum depth to traverse subdirectories', parseInt)
.option('--no-traverse-up', 'Prevent traversing above the specified directory')
.option('--boundary <path>', 'Explicit boundary path to limit scanning scope')
.option('--generate-toc', 'Generate table of contents for each indexed file')
.option('--toc-min-depth <number>', 'Minimum heading level for TOC (1-6)', parseInt, 1)
.option('--toc-max-depth <number>', 'Maximum heading level for TOC (1-6)', parseInt, 6)
.option('--toc-include-line-numbers', 'Include line numbers in table of contents')
.option('-d, --dry-run', 'Show what would be generated without creating files')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.addHelpText(
'after',
`
Examples:
$ markmv index --type links --strategy directory --generate-toc
$ markmv index docs/ --type hybrid --generate-toc --toc-min-depth 2 --toc-max-depth 4
$ markmv index --generate-toc --toc-include-line-numbers --dry-run
$ markmv index --type links --strategy metadata --location all --generate-toc
Table of Contents Options:
--generate-toc Enable TOC generation for indexed files
--toc-min-depth <number> Minimum heading level to include (1-6, default: 1)
--toc-max-depth <number> Maximum heading level to include (1-6, default: 6)
--toc-include-line-numbers Include line numbers in TOC entries
Index Types:
links Generate simple link lists with optional TOC
import Generate Claude import syntax (@path)
embed Generate embedded content (Obsidian style)
hybrid Generate linked headers with descriptions and optional TOC
Organization Strategies:
directory Group by directory structure
metadata Group by frontmatter categories
manual Custom organization (extensible)
Index Placement:
all Create index in every directory
root Create index only in root directory
branch Create index in directories with subdirectories
existing Update only existing index files`
)
.action(indexCommand);
program
.command('barrel')
.description('Generate barrel files for themed content aggregation (alias for index)')
.argument('[directory]', 'Directory to generate barrel files for', '.')
.option('-t, --type <type>', 'Barrel type: links|import|embed|hybrid', 'links')
.option(
'-s, --strategy <strategy>',
'Organization strategy: directory|metadata|manual',
'directory'
)
.option('-l, --location <location>', 'Barrel placement: all|root|branch|existing', 'root')
.option('-n, --name <name>', 'Barrel filename', 'index.md')
.option('--embed-style <style>', 'Embed style for embed type: obsidian|markdown', 'obsidian')
.option('--template <file>', 'Custom template file')
.option('--max-depth <number>', 'Maximum depth to traverse subdirectories', parseInt)
.option('--no-traverse-up', 'Prevent traversing above the specified directory')
.option('--boundary <path>', 'Explicit boundary path to limit scanning scope')
.option('--generate-toc', 'Generate table of contents for each indexed file')
.option('--toc-min-depth <number>', 'Minimum heading level for TOC (1-6)', parseInt, 1)
.option('--toc-max-depth <number>', 'Maximum heading level for TOC (1-6)', parseInt, 6)
.option('--toc-include-line-numbers', 'Include line numbers in table of contents')
.option('-d, --dry-run', 'Show what would be generated without creating files')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.addHelpText(
'after',
`
Examples:
$ markmv barrel --type links --strategy directory --generate-toc
$ markmv barrel docs/ --type hybrid --generate-toc --toc-min-depth 2 --toc-max-depth 4
$ markmv barrel --generate-toc --toc-include-line-numbers --dry-run
$ markmv barrel --type links --strategy metadata --location all --generate-toc
Barrel Types:
links Generate simple link lists with optional TOC
import Generate Claude import syntax (@path)
embed Generate embedded content (Obsidian style)
hybrid Generate linked headers with descriptions and optional TOC
Organization Strategies:
directory Group by directory structure
metadata Group by frontmatter categories
manual Custom organization (extensible)
Barrel Placement:
all Create barrel in every directory
root Create barrel only in root directory
branch Create barrel in directories with subdirectories
existing Update only existing barrel files
Note: This is an alias for the 'index' command with barrel-focused terminology.`
)
.action(indexCommand);
program
.command('toc')
.description('Generate and insert table of contents into markdown files')
.argument('<files...>', 'Markdown files to process (supports globs like *.md, **/*.md)')
.option('--min-depth <number>', 'Minimum heading level to include (1-6)', parseInt, 1)
.option('--max-depth <number>', 'Maximum heading level to include (1-6)', parseInt, 6)
.option('--include-line-numbers', 'Include line numbers in TOC entries')
.option(
'--position <position>',
'TOC position: top|after-title|before-content|replace',
'after-title'
)
.option('--title <title>', 'TOC title', 'Table of Contents')
.option('--heading-level <level>', 'TOC heading level (1-6)', parseInt, 2)
.option('--marker <marker>', 'Custom marker for TOC replacement (requires --position replace)')
.option('--skip-empty', "Skip files that don't have any headings", true)
.option('-d, --dry-run', 'Show what would be changed without making changes')
.option('-v, --verbose', 'Show detailed output')
.option('--json', 'Output results in JSON format')
.addHelpText(
'after',
`
Examples:
$ markmv toc README.md
$ markmv toc docs/*.md --position after-title --min-depth 2 --max-depth 4
$ markmv toc file.md --position replace --marker "<!-- TOC -->"
$ markmv toc **/*.md --title "Contents" --heading-level 3 --include-line-numbers
Position Options:
top Insert TOC at the very beginning of the file
after-title Insert TOC after the first heading (default)
before-content Insert TOC before main content (after frontmatter)
replace Replace existing TOC using marker or auto-detection
TOC Customization:
--title <title> Custom TOC title (default: "Table of Contents")
--heading-level <level> TOC heading level 1-6 (default: 2, creates ## title)
--marker <marker> Custom marker for replacement (e.g., "<!-- TOC -->")
--min-depth <number> Minimum heading level to include (1-6, default: 1)
--max-depth <number> Maximum heading level to include (1-6, default: 6)
--include-line-numbers Include line numbers in TOC entries`
)
.action(tocCommand);
program
.command('validate')
.description('Find broken links in markdown files')
.argument(
'[files...]',
'Markdown files to validate (supports globs like *.md, **/*.md, defaults to current directory)'
)
.option(
'--link-types <types>',
'Comma-separated link types to check: internal,external,anchor,image,reference,claude-import'
)
.option('--check-external', 'Enable external HTTP/HTTPS link validation', false)
.option('--external-timeout <ms>', 'Timeout for external link validation (ms)', parseInt, 5000)
.option('--strict-internal', 'Treat missing internal files as errors', true)
.option('--check-claude-imports', 'Validate Claude import paths', true)
.option('--check-circular', 'Check for circular references in file dependencies', false)
.option('--max-depth <number>', 'Maximum depth to traverse subdirectories', parseInt)
.option('--only-broken', 'Show only broken links, not all validation results', true)
.option('--group-by <method>', 'Group results by: file|type', 'file')
.option('--include-context', 'Include line numbers and context in output', false)
.option('-v, --verbose', 'Show detailed output with processing information')
.option('--json', 'Output results in JSON format')
.addHelpText(
'after',
`
Examples:
$ markmv validate # Validate current directory
$ markmv validate . # Validate current directory
$ markmv validate ./ # Validate current directory
$ markmv validate docs/**/*.md --check-external --verbose
$ markmv validate README.md --link-types internal,image --include-context
$ markmv validate **/*.md --group-by type --only-broken
$ markmv validate docs/ --check-circular --strict-internal
Link Types:
internal Links to other markdown files
external HTTP/HTTPS URLs
anchor Same-file section links (#heading)
image Image references (local and external)
reference Reference-style links ([text][ref])
claude-import Claude @import syntax (@path/to/file)
Output Options:
--group-by file Group broken links by file (default)
--group-by type Group broken links by link type`
)
.action(validateCommand);
program
.command('check-links')
.description('Check external HTTP/HTTPS links in markdown files')
.argument('[files...]', 'Markdown files to check (supports globs, defaults to current directory)')
.option('--timeout <ms>', 'Timeout for external link validation (ms)', parseInt, 10000)
.option('--retry <count>', 'Number of retry attempts for failed requests', parseInt, 3)
.option('--retry-delay <ms>', 'Delay between retry attempts (ms)', parseInt, 1000)
.option('--concurrency <count>', 'Maximum concurrent requests', parseInt, 10)
.option('--method <method>', 'HTTP method to use (HEAD|GET)', 'HEAD')
.option('--no-follow-redirects', 'Do not follow HTTP redirects')
.option('--ignore-status <codes>', 'Comma-separated HTTP status codes to ignore', '403,999')
.option('--ignore-patterns <patterns>', 'Comma-separated regex patterns to ignore')
.option('--no-cache', 'Disable result caching')
.option('--cache-duration <minutes>', 'Cache duration in minutes', parseInt, 60)
.option('--no-progress', 'Hide progress indicator')
.option('--format <format>', 'Output format: text|json|markdown|csv', 'text')
.option('--include-response-times', 'Include response times in output')
.option('--include-headers', 'Include HTTP headers in detailed output')
.option('--max-depth <number>', 'Maximum depth to traverse subdirectories', parseInt)
.option('--group-by <method>', 'Group results by: file|status|domain', 'file')
.option('--output <file>', 'Output file path for results')
.option('--config <file>', 'Configuration file path')
.option('-v, --verbose', 'Show detailed output with processing information')
.option('-d, --dry-run', 'Show what would be checked without making requests')
.addHelpText(
'after',
`
Examples:
$ markmv check-links # Check current directory
$ markmv check-links docs/**/*.md --verbose # Check with detailed output
$ markmv check-links README.md --timeout 15000 # Custom timeout
$ markmv check-links --retry 5 --retry-delay 2000 # Custom retry logic
$ markmv check-links --format json > results.json # JSON output
$ markmv check-links --format markdown > report.md # Markdown report
$ markmv check-links --group-by domain --verbose # Group by domain
$ markmv check-links --ignore-patterns "localhost,127.0.0.1" # Ignore patterns
$ markmv check-links --concurrency 20 --method GET # High concurrency with GET
Features:
🔄 Smart retry logic for temporary failures
⚡ Configurable concurrency for parallel checking
🗄️ Response caching to avoid re-checking recently validated URLs
📊 Multiple output formats (text, JSON, markdown, CSV)
📈 Progress indicators for large documentation sets
🤖 Bot-detection handling (ignores 403s by default)
⏱️ Response time measurement and statistics
🌐 Domain-based grouping and analysis
Output Formats:
text Human-readable console output (default)
json Structured JSON for programmatic use
markdown Formatted markdown report
csv Comma-separated values for spreadsheets
Grouping Options:
file Group results by file (default)
status Group results by HTTP status code
domain Group results by domain name`
)
.action(checkLinksCommand);
program.parse();