@lexbuild/cli 1.9.3 → 1.10.0
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 +77 -77
- package/dist/index.js +115 -38
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,151 +2,129 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@lexbuild/cli)
|
|
4
4
|
[](https://github.com/chris-c-thomas/LexBuild/actions/workflows/ci.yml)
|
|
5
|
-
[](https://github.com/chris-c-thomas/LexBuild)
|
|
5
|
+
[](https://github.com/chris-c-thomas/LexBuild/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
It provides the CLI entry point for downloading and converting legal texts. Built on [`@lexbuild/core`](https://www.npmjs.com/package/@lexbuild/core) for shared parsing and rendering infrastructure, [`@lexbuild/usc`](https://www.npmjs.com/package/@lexbuild/usc) for [United States Code](https://uscode.house.gov/) support, and [`@lexbuild/ecfr`](https://www.npmjs.com/package/@lexbuild/ecfr) for [eCFR](https://www.ecfr.gov/) (Code of Federal Regulations) support.
|
|
7
|
+
Download and convert U.S. legal XML into structured Markdown optimized for AI, RAG pipelines, and semantic search. Supports the [U.S. Code](https://uscode.house.gov/) (54 titles, 60,000+ sections) and the [eCFR](https://www.ecfr.gov/) (50 titles, 200,000+ sections).
|
|
10
8
|
|
|
11
9
|
## Install
|
|
12
10
|
|
|
13
|
-
Install globally
|
|
14
|
-
|
|
15
11
|
```bash
|
|
12
|
+
# Global install
|
|
16
13
|
npm install -g @lexbuild/cli
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Run directly with npx:
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
npx @lexbuild/cli
|
|
23
|
-
npx @lexbuild/cli convert-usc --all
|
|
15
|
+
# Or run directly
|
|
16
|
+
npx @lexbuild/cli --help
|
|
24
17
|
```
|
|
25
18
|
|
|
26
19
|
## Quick Start
|
|
27
20
|
|
|
28
|
-
### U.S. Code
|
|
29
|
-
|
|
30
21
|
```bash
|
|
31
|
-
#
|
|
32
|
-
lexbuild download-usc --all
|
|
22
|
+
# U.S. Code — download and convert all 54 titles
|
|
23
|
+
lexbuild download-usc --all
|
|
24
|
+
lexbuild convert-usc --all
|
|
33
25
|
|
|
34
|
-
#
|
|
35
|
-
lexbuild download-
|
|
36
|
-
|
|
37
|
-
# Download and convert a range
|
|
38
|
-
lexbuild download-usc --titles 1-5 && lexbuild convert-usc --titles 1-5
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### eCFR (Code of Federal Regulations)
|
|
26
|
+
# eCFR — download and convert all 50 titles
|
|
27
|
+
lexbuild download-ecfr --all
|
|
28
|
+
lexbuild convert-ecfr --all
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
lexbuild download-ecfr --all && lexbuild convert-ecfr --all
|
|
46
|
-
|
|
47
|
-
# Download and convert a single title
|
|
30
|
+
# Start small — a single title
|
|
31
|
+
lexbuild download-usc --titles 1 && lexbuild convert-usc --titles 1
|
|
48
32
|
lexbuild download-ecfr --titles 17 && lexbuild convert-ecfr --titles 17
|
|
49
33
|
```
|
|
50
34
|
|
|
51
35
|
## Commands
|
|
52
36
|
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
#### `lexbuild download-usc`
|
|
37
|
+
### `download-usc`
|
|
56
38
|
|
|
57
|
-
|
|
39
|
+
Download U.S. Code XML from the OLRC. Auto-detects the latest release point.
|
|
58
40
|
|
|
59
41
|
```bash
|
|
60
|
-
lexbuild download-usc --
|
|
61
|
-
lexbuild download-usc --titles 1-5,8,11
|
|
62
|
-
lexbuild download-usc --all
|
|
42
|
+
lexbuild download-usc --all # All 54 titles
|
|
43
|
+
lexbuild download-usc --titles 1-5,8,11 # Specific titles
|
|
44
|
+
lexbuild download-usc --all --release-point 119-73not60 # Pin a release
|
|
63
45
|
```
|
|
64
46
|
|
|
65
47
|
| Option | Default | Description |
|
|
66
48
|
|--------|---------|-------------|
|
|
67
|
-
| `--titles <spec>` | — | Title(s)
|
|
68
|
-
| `--all` | — | Download all 54 titles |
|
|
49
|
+
| `--titles <spec>` | — | Title(s): `1`, `1-5`, `1-5,8,11` |
|
|
50
|
+
| `--all` | — | Download all 54 titles (single bulk zip) |
|
|
69
51
|
| `-o, --output <dir>` | `./downloads/usc/xml` | Output directory |
|
|
70
|
-
| `--release-point <id>` |
|
|
52
|
+
| `--release-point <id>` | auto-detected | Pin a specific OLRC release point |
|
|
71
53
|
|
|
72
|
-
|
|
54
|
+
### `convert-usc`
|
|
73
55
|
|
|
74
56
|
Convert downloaded USC XML to Markdown.
|
|
75
57
|
|
|
76
58
|
```bash
|
|
77
|
-
lexbuild convert-usc --
|
|
78
|
-
lexbuild convert-usc --
|
|
79
|
-
lexbuild convert-usc
|
|
80
|
-
lexbuild convert-usc
|
|
81
|
-
lexbuild convert-usc --titles 1 -g title # Title-level output (single file)
|
|
82
|
-
lexbuild convert-usc --titles 1 --link-style canonical # OLRC website links
|
|
83
|
-
lexbuild convert-usc --titles 42 --dry-run # Preview without writing
|
|
59
|
+
lexbuild convert-usc --all # All downloaded titles
|
|
60
|
+
lexbuild convert-usc --titles 1 -g chapter # Chapter-level output
|
|
61
|
+
lexbuild convert-usc --titles 26 --dry-run # Preview without writing
|
|
62
|
+
lexbuild convert-usc ./downloads/usc/xml/usc01.xml # Direct file path
|
|
84
63
|
```
|
|
85
64
|
|
|
86
65
|
| Option | Default | Description |
|
|
87
66
|
|--------|---------|-------------|
|
|
88
67
|
| `--titles <spec>` | — | Title(s) to convert |
|
|
89
68
|
| `--all` | — | Convert all titles in input directory |
|
|
90
|
-
| `-i, --input-dir <dir>` | `./downloads/usc/xml` | Input
|
|
69
|
+
| `-i, --input-dir <dir>` | `./downloads/usc/xml` | Input XML directory |
|
|
91
70
|
| `-o, --output <dir>` | `./output` | Output directory |
|
|
92
|
-
| `-g, --granularity
|
|
93
|
-
| `--link-style
|
|
71
|
+
| `-g, --granularity` | `section` | `section`, `chapter`, or `title` |
|
|
72
|
+
| `--link-style` | `plaintext` | `plaintext`, `canonical`, or `relative` |
|
|
94
73
|
| `--no-include-source-credits` | — | Exclude source credits |
|
|
95
74
|
| `--no-include-notes` | — | Exclude all notes |
|
|
96
75
|
| `--include-editorial-notes` | — | Include editorial notes only |
|
|
97
76
|
| `--include-statutory-notes` | — | Include statutory notes only |
|
|
98
77
|
| `--include-amendments` | — | Include amendment notes only |
|
|
99
|
-
| `--dry-run` | — | Parse and report without writing
|
|
100
|
-
| `-v, --verbose` | — | Verbose
|
|
78
|
+
| `--dry-run` | — | Parse and report without writing |
|
|
79
|
+
| `-v, --verbose` | — | Verbose file output |
|
|
101
80
|
|
|
102
|
-
###
|
|
81
|
+
### `download-ecfr`
|
|
103
82
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Fetch eCFR XML files from govinfo.
|
|
83
|
+
Download eCFR XML. Defaults to the ecfr.gov API (daily-updated); govinfo bulk data available as fallback.
|
|
107
84
|
|
|
108
85
|
```bash
|
|
109
|
-
lexbuild download-ecfr --titles
|
|
110
|
-
lexbuild download-ecfr --titles 1-5,17
|
|
111
|
-
lexbuild download-ecfr --all
|
|
86
|
+
lexbuild download-ecfr --all # All 50 titles (eCFR API)
|
|
87
|
+
lexbuild download-ecfr --titles 1-5,17 # Specific titles
|
|
88
|
+
lexbuild download-ecfr --all --date 2026-01-01 # Point-in-time download
|
|
89
|
+
lexbuild download-ecfr --all --source govinfo # Govinfo bulk fallback
|
|
112
90
|
```
|
|
113
91
|
|
|
114
92
|
| Option | Default | Description |
|
|
115
93
|
|--------|---------|-------------|
|
|
116
|
-
| `--titles <spec>` | — | Title(s)
|
|
94
|
+
| `--titles <spec>` | — | Title(s): `1`, `1-5`, `1-5,17` |
|
|
117
95
|
| `--all` | — | Download all 50 titles |
|
|
118
96
|
| `-o, --output <dir>` | `./downloads/ecfr/xml` | Output directory |
|
|
97
|
+
| `--source` | `ecfr-api` | `ecfr-api` (daily) or `govinfo` (bulk) |
|
|
98
|
+
| `--date <YYYY-MM-DD>` | current | Point-in-time date (ecfr-api only) |
|
|
119
99
|
|
|
120
|
-
|
|
100
|
+
### `convert-ecfr`
|
|
121
101
|
|
|
122
102
|
Convert downloaded eCFR XML to Markdown.
|
|
123
103
|
|
|
124
104
|
```bash
|
|
125
|
-
lexbuild convert-ecfr --
|
|
126
|
-
lexbuild convert-ecfr --
|
|
127
|
-
lexbuild convert-ecfr
|
|
128
|
-
lexbuild convert-ecfr
|
|
129
|
-
lexbuild convert-ecfr --titles 17 -g title # Title-level output
|
|
130
|
-
lexbuild convert-ecfr --all --dry-run # Preview without writing
|
|
105
|
+
lexbuild convert-ecfr --all # All downloaded titles
|
|
106
|
+
lexbuild convert-ecfr --titles 17 -g part # Part-level output
|
|
107
|
+
lexbuild convert-ecfr --all --dry-run # Preview without writing
|
|
108
|
+
lexbuild convert-ecfr ./downloads/ecfr/xml/ECFR-title17.xml # Direct file path
|
|
131
109
|
```
|
|
132
110
|
|
|
133
111
|
| Option | Default | Description |
|
|
134
112
|
|--------|---------|-------------|
|
|
135
113
|
| `--titles <spec>` | — | Title(s) to convert |
|
|
136
114
|
| `--all` | — | Convert all titles in input directory |
|
|
137
|
-
| `-i, --input-dir <dir>` | `./downloads/ecfr/xml` | Input
|
|
115
|
+
| `-i, --input-dir <dir>` | `./downloads/ecfr/xml` | Input XML directory |
|
|
138
116
|
| `-o, --output <dir>` | `./output` | Output directory |
|
|
139
|
-
| `-g, --granularity
|
|
140
|
-
| `--link-style
|
|
117
|
+
| `-g, --granularity` | `section` | `section`, `part`, `chapter`, or `title` |
|
|
118
|
+
| `--link-style` | `plaintext` | `plaintext`, `canonical`, or `relative` |
|
|
141
119
|
| `--no-include-source-credits` | — | Exclude source credits |
|
|
142
120
|
| `--no-include-notes` | — | Exclude all notes |
|
|
143
|
-
| `--include-editorial-notes` | — | Include editorial notes only |
|
|
144
|
-
| `--include-statutory-notes` | — | Include statutory
|
|
121
|
+
| `--include-editorial-notes` | — | Include editorial/regulatory notes only |
|
|
122
|
+
| `--include-statutory-notes` | — | Include statutory notes only |
|
|
145
123
|
| `--include-amendments` | — | Include amendment notes only |
|
|
146
|
-
| `--dry-run` | — | Parse and report without writing
|
|
147
|
-
| `-v, --verbose` | — | Verbose
|
|
124
|
+
| `--dry-run` | — | Parse and report without writing |
|
|
125
|
+
| `-v, --verbose` | — | Verbose file output |
|
|
148
126
|
|
|
149
|
-
## Output
|
|
127
|
+
## Output Structure
|
|
150
128
|
|
|
151
129
|
### U.S. Code
|
|
152
130
|
|
|
@@ -165,11 +143,33 @@ lexbuild convert-ecfr --all --dry-run # Preview without
|
|
|
165
143
|
| `chapter` | `output/ecfr/title-17/chapter-IV/chapter-IV.md` |
|
|
166
144
|
| `title` | `output/ecfr/title-17.md` |
|
|
167
145
|
|
|
168
|
-
|
|
146
|
+
Every file includes YAML frontmatter with source metadata (`source`, `legal_status`, identifier, hierarchy context) followed by the legal text in Markdown. Section and chapter/part granularities generate `_meta.json` sidecar files and `README.md` summaries per title.
|
|
169
147
|
|
|
170
148
|
## Performance
|
|
171
149
|
|
|
172
|
-
The full U.S. Code — all 54 titles, 60,000+ sections, ~85 million estimated tokens — converts in about 20
|
|
150
|
+
The full U.S. Code — all 54 titles, 60,000+ sections, ~85 million estimated tokens — converts in about 20–30 seconds on modern hardware. SAX streaming keeps memory bounded for even the largest titles (100MB+ XML).
|
|
151
|
+
|
|
152
|
+
## Compatibility
|
|
153
|
+
|
|
154
|
+
- **Node.js** >= 22
|
|
155
|
+
- **ESM only** — no CommonJS build
|
|
156
|
+
|
|
157
|
+
## Monorepo Context
|
|
158
|
+
|
|
159
|
+
This is the published CLI for the [LexBuild](https://github.com/chris-c-thomas/LexBuild) monorepo. It depends on [`@lexbuild/core`](https://www.npmjs.com/package/@lexbuild/core), [`@lexbuild/usc`](https://www.npmjs.com/package/@lexbuild/usc), and [`@lexbuild/ecfr`](https://www.npmjs.com/package/@lexbuild/ecfr) for all conversion and download logic.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pnpm turbo build --filter=@lexbuild/cli
|
|
163
|
+
pnpm turbo typecheck --filter=@lexbuild/cli
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Related Packages
|
|
167
|
+
|
|
168
|
+
| Package | Description |
|
|
169
|
+
|---------|-------------|
|
|
170
|
+
| [`@lexbuild/core`](https://www.npmjs.com/package/@lexbuild/core) | Shared parsing, AST, and rendering infrastructure |
|
|
171
|
+
| [`@lexbuild/usc`](https://www.npmjs.com/package/@lexbuild/usc) | U.S. Code converter — programmatic API |
|
|
172
|
+
| [`@lexbuild/ecfr`](https://www.npmjs.com/package/@lexbuild/ecfr) | eCFR converter — programmatic API |
|
|
173
173
|
|
|
174
174
|
## License
|
|
175
175
|
|
package/dist/index.js
CHANGED
|
@@ -435,16 +435,18 @@ Examples:
|
|
|
435
435
|
// src/commands/download-usc.ts
|
|
436
436
|
import { Command as Command2 } from "commander";
|
|
437
437
|
import { relative as relative2, resolve as resolve2 } from "path";
|
|
438
|
-
import { downloadTitles,
|
|
439
|
-
var downloadUscCommand = new Command2("download-usc").description("Download U.S. Code XML from OLRC").option("-o, --output <dir>", "Download directory", "./downloads/usc/xml").option("--titles <spec>", "Title(s) to download: 1, 1-5, or 1-5,8,11").option("--all", "Download all 54 titles (single bulk zip)", false).option("--release-point <id>", "OLRC release point
|
|
438
|
+
import { downloadTitles, detectLatestReleasePoint, FALLBACK_RELEASE_POINT } from "@lexbuild/usc";
|
|
439
|
+
var downloadUscCommand = new Command2("download-usc").description("Download U.S. Code XML from OLRC").option("-o, --output <dir>", "Download directory", "./downloads/usc/xml").option("--titles <spec>", "Title(s) to download: 1, 1-5, or 1-5,8,11").option("--all", "Download all 54 titles (single bulk zip)", false).option("--release-point <id>", "OLRC release point (auto-detected if omitted)").addHelpText(
|
|
440
440
|
"after",
|
|
441
441
|
`
|
|
442
442
|
Examples:
|
|
443
|
-
$ lexbuild download-usc --all Download all 54 titles
|
|
443
|
+
$ lexbuild download-usc --all Download all 54 titles (latest release)
|
|
444
444
|
$ lexbuild download-usc --titles 1 Download Title 1 only
|
|
445
445
|
$ lexbuild download-usc --titles 1-5,8,11 Download specific titles
|
|
446
446
|
$ lexbuild download-usc --all -o ./my-xml Custom output directory
|
|
447
|
+
$ lexbuild download-usc --all --release-point 119-73not60 Pin a specific release
|
|
447
448
|
|
|
449
|
+
The latest release point is auto-detected from the OLRC download page.
|
|
448
450
|
Source: https://uscode.house.gov/download/download.shtml`
|
|
449
451
|
).action(async (options) => {
|
|
450
452
|
if (!options.titles && !options.all) {
|
|
@@ -462,6 +464,19 @@ Source: https://uscode.house.gov/download/download.shtml`
|
|
|
462
464
|
}
|
|
463
465
|
const outputDir = resolve2(options.output);
|
|
464
466
|
const titleCount = titles ? titles.length : 54;
|
|
467
|
+
let releasePoint = options.releasePoint;
|
|
468
|
+
if (!releasePoint) {
|
|
469
|
+
const detectSpinner = createSpinner("Detecting latest OLRC release point...");
|
|
470
|
+
detectSpinner.start();
|
|
471
|
+
const detected = await detectLatestReleasePoint();
|
|
472
|
+
if (detected) {
|
|
473
|
+
releasePoint = detected.releasePoint;
|
|
474
|
+
detectSpinner.succeed(`Release point: ${detected.releasePoint}`);
|
|
475
|
+
} else {
|
|
476
|
+
releasePoint = FALLBACK_RELEASE_POINT;
|
|
477
|
+
detectSpinner.warn(`Could not detect release point, using fallback: ${releasePoint}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
465
480
|
const label = titleCount === 1 ? `Downloading Title ${titles?.[0]}` : `Downloading ${titleCount} titles`;
|
|
466
481
|
const spinner = createSpinner(`${label}...`);
|
|
467
482
|
spinner.start();
|
|
@@ -470,7 +485,7 @@ Source: https://uscode.house.gov/download/download.shtml`
|
|
|
470
485
|
const result = await downloadTitles({
|
|
471
486
|
outputDir,
|
|
472
487
|
titles,
|
|
473
|
-
releasePoint
|
|
488
|
+
releasePoint
|
|
474
489
|
});
|
|
475
490
|
const elapsed = performance.now() - startTime;
|
|
476
491
|
spinner.stop();
|
|
@@ -480,10 +495,11 @@ Source: https://uscode.house.gov/download/download.shtml`
|
|
|
480
495
|
relative2(outputDir, file.filePath) || file.filePath
|
|
481
496
|
]);
|
|
482
497
|
const totalBytes = result.files.reduce((sum, f) => sum + f.size, 0);
|
|
498
|
+
const rpLabel = options.releasePoint ? result.releasePoint : `${result.releasePoint} (auto-detected)`;
|
|
483
499
|
const output = summaryBlock({
|
|
484
500
|
title: "lexbuild \u2014 Download Summary",
|
|
485
501
|
rows: [
|
|
486
|
-
["Release Point",
|
|
502
|
+
["Release Point", rpLabel],
|
|
487
503
|
["Directory", relative2(process.cwd(), outputDir) || outputDir]
|
|
488
504
|
]
|
|
489
505
|
});
|
|
@@ -782,22 +798,44 @@ Examples:
|
|
|
782
798
|
// src/commands/download-ecfr.ts
|
|
783
799
|
import { Command as Command4 } from "commander";
|
|
784
800
|
import { relative as relative4, resolve as resolve4 } from "path";
|
|
785
|
-
import { downloadEcfrTitles } from "@lexbuild/ecfr";
|
|
786
|
-
var downloadEcfrCommand = new Command4("download-ecfr").description("Download eCFR XML from govinfo
|
|
801
|
+
import { downloadEcfrTitles, downloadEcfrTitlesFromApi } from "@lexbuild/ecfr";
|
|
802
|
+
var downloadEcfrCommand = new Command4("download-ecfr").description("Download eCFR XML from govinfo or eCFR API").option("-o, --output <dir>", "Download directory", "./downloads/ecfr/xml").option("--titles <spec>", "Title(s) to download: 1, 1-5, or 1-5,8,17").option("--all", "Download all 50 eCFR titles", false).option(
|
|
803
|
+
"--source <source>",
|
|
804
|
+
"Download source: ecfr-api (daily-updated) or govinfo (bulk data)",
|
|
805
|
+
"ecfr-api"
|
|
806
|
+
).option("--date <YYYY-MM-DD>", "Point-in-time date (ecfr-api source only)").addHelpText(
|
|
787
807
|
"after",
|
|
788
808
|
`
|
|
789
809
|
Examples:
|
|
790
|
-
$ lexbuild download-ecfr --all
|
|
791
|
-
$ lexbuild download-ecfr --titles 1
|
|
792
|
-
$ lexbuild download-ecfr --titles 1-5,17
|
|
793
|
-
$ lexbuild download-ecfr --all
|
|
810
|
+
$ lexbuild download-ecfr --all Download all titles from eCFR API
|
|
811
|
+
$ lexbuild download-ecfr --titles 1 Download Title 1 only
|
|
812
|
+
$ lexbuild download-ecfr --titles 1-5,17 Download specific titles
|
|
813
|
+
$ lexbuild download-ecfr --all --date 2026-01-01 Point-in-time download
|
|
814
|
+
$ lexbuild download-ecfr --all --source govinfo Download from govinfo bulk data
|
|
794
815
|
|
|
795
|
-
|
|
816
|
+
Sources:
|
|
817
|
+
ecfr-api \u2014 eCFR API from ecfr.gov (default, daily-updated, point-in-time support)
|
|
818
|
+
govinfo \u2014 Bulk XML from govinfo.gov (updates irregularly)`
|
|
796
819
|
).action(async (options) => {
|
|
797
820
|
if (!options.titles && !options.all) {
|
|
798
821
|
console.error(error("Specify --titles <spec> or --all (e.g. --titles 1-5,17)"));
|
|
799
822
|
process.exit(1);
|
|
800
823
|
}
|
|
824
|
+
const validSources = ["govinfo", "ecfr-api"];
|
|
825
|
+
if (!validSources.includes(options.source)) {
|
|
826
|
+
console.error(
|
|
827
|
+
error(`Invalid source "${options.source}". Valid sources: ${validSources.join(", ")}`)
|
|
828
|
+
);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
if (options.date && options.source !== "ecfr-api") {
|
|
832
|
+
console.error(error("--date is only supported with --source ecfr-api"));
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
if (options.date && !/^\d{4}-\d{2}-\d{2}$/.test(options.date)) {
|
|
836
|
+
console.error(error("--date must be in YYYY-MM-DD format"));
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
801
839
|
let titles;
|
|
802
840
|
if (options.titles) {
|
|
803
841
|
try {
|
|
@@ -809,42 +847,81 @@ Source: https://www.govinfo.gov/bulkdata/ECFR`
|
|
|
809
847
|
}
|
|
810
848
|
const outputDir = resolve4(options.output);
|
|
811
849
|
const titleCount = titles ? titles.length : 50;
|
|
812
|
-
const
|
|
850
|
+
const sourceLabel = options.source === "ecfr-api" ? "eCFR API" : "govinfo";
|
|
851
|
+
const label = titleCount === 1 ? `Downloading eCFR Title ${titles?.[0]} from ${sourceLabel}` : `Downloading ${titleCount} eCFR titles from ${sourceLabel}`;
|
|
813
852
|
const spinner = createSpinner(`${label}...`);
|
|
814
853
|
spinner.start();
|
|
815
854
|
const startTime = performance.now();
|
|
816
855
|
try {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const elapsed = performance.now() - startTime;
|
|
822
|
-
spinner.stop();
|
|
823
|
-
const fileRows = result.files.map((file) => [
|
|
824
|
-
String(file.titleNumber),
|
|
825
|
-
formatBytes(file.size),
|
|
826
|
-
relative4(outputDir, file.path) || file.path
|
|
827
|
-
]);
|
|
828
|
-
const output = summaryBlock({
|
|
829
|
-
title: "lexbuild \u2014 eCFR Download Summary",
|
|
830
|
-
rows: [
|
|
831
|
-
["Source", "govinfo.gov/bulkdata/ECFR"],
|
|
832
|
-
["Directory", relative4(process.cwd(), outputDir) || outputDir]
|
|
833
|
-
]
|
|
834
|
-
});
|
|
835
|
-
process.stdout.write(output);
|
|
836
|
-
if (fileRows.length > 0) {
|
|
837
|
-
console.log(dataTable(["Title", "Size", "File"], fileRows));
|
|
856
|
+
if (options.source === "ecfr-api") {
|
|
857
|
+
await downloadFromApi(options, titles, outputDir, spinner, startTime);
|
|
858
|
+
} else {
|
|
859
|
+
await downloadFromGovinfo(titles, outputDir, spinner, startTime);
|
|
838
860
|
}
|
|
839
|
-
const titleWord = result.titlesDownloaded === 1 ? "title" : "titles";
|
|
840
|
-
const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;
|
|
841
|
-
console.log(` ${success(summary)}`);
|
|
842
|
-
console.log("");
|
|
843
861
|
} catch (err) {
|
|
844
862
|
spinner.fail(err instanceof Error ? err.message : String(err));
|
|
845
863
|
process.exit(1);
|
|
846
864
|
}
|
|
847
865
|
});
|
|
866
|
+
async function downloadFromGovinfo(titles, outputDir, spinner, startTime) {
|
|
867
|
+
const result = await downloadEcfrTitles({
|
|
868
|
+
output: outputDir,
|
|
869
|
+
titles
|
|
870
|
+
});
|
|
871
|
+
const elapsed = performance.now() - startTime;
|
|
872
|
+
spinner.stop();
|
|
873
|
+
const fileRows = result.files.map((file) => [
|
|
874
|
+
String(file.titleNumber),
|
|
875
|
+
formatBytes(file.size),
|
|
876
|
+
relative4(outputDir, file.path) || file.path
|
|
877
|
+
]);
|
|
878
|
+
const output = summaryBlock({
|
|
879
|
+
title: "lexbuild \u2014 eCFR Download Summary",
|
|
880
|
+
rows: [
|
|
881
|
+
["Source", "govinfo.gov/bulkdata/ECFR"],
|
|
882
|
+
["Directory", relative4(process.cwd(), outputDir) || outputDir]
|
|
883
|
+
]
|
|
884
|
+
});
|
|
885
|
+
process.stdout.write(output);
|
|
886
|
+
if (fileRows.length > 0) {
|
|
887
|
+
console.log(dataTable(["Title", "Size", "File"], fileRows));
|
|
888
|
+
}
|
|
889
|
+
const titleWord = result.titlesDownloaded === 1 ? "title" : "titles";
|
|
890
|
+
const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;
|
|
891
|
+
console.log(` ${success(summary)}`);
|
|
892
|
+
console.log("");
|
|
893
|
+
}
|
|
894
|
+
async function downloadFromApi(options, titles, outputDir, spinner, startTime) {
|
|
895
|
+
const result = await downloadEcfrTitlesFromApi({
|
|
896
|
+
output: outputDir,
|
|
897
|
+
titles,
|
|
898
|
+
date: options.date
|
|
899
|
+
});
|
|
900
|
+
const elapsed = performance.now() - startTime;
|
|
901
|
+
spinner.stop();
|
|
902
|
+
const fileRows = result.files.map((file) => [
|
|
903
|
+
String(file.titleNumber),
|
|
904
|
+
formatBytes(file.size),
|
|
905
|
+
relative4(outputDir, file.path) || file.path
|
|
906
|
+
]);
|
|
907
|
+
const summaryRows = [
|
|
908
|
+
["Source", "ecfr.gov/api/versioner/v1"],
|
|
909
|
+
["As of date", result.asOfDate],
|
|
910
|
+
["Directory", relative4(process.cwd(), outputDir) || outputDir]
|
|
911
|
+
];
|
|
912
|
+
const output = summaryBlock({
|
|
913
|
+
title: "lexbuild \u2014 eCFR Download Summary",
|
|
914
|
+
rows: summaryRows
|
|
915
|
+
});
|
|
916
|
+
process.stdout.write(output);
|
|
917
|
+
if (fileRows.length > 0) {
|
|
918
|
+
console.log(dataTable(["Title", "Size", "File"], fileRows));
|
|
919
|
+
}
|
|
920
|
+
const titleWord = result.titlesDownloaded === 1 ? "title" : "titles";
|
|
921
|
+
const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;
|
|
922
|
+
console.log(` ${success(summary)}`);
|
|
923
|
+
console.log("");
|
|
924
|
+
}
|
|
848
925
|
|
|
849
926
|
// src/index.ts
|
|
850
927
|
var require2 = createRequire(import.meta.url);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/convert-usc.ts","../src/ui.ts","../src/parse-titles.ts","../src/commands/download-usc.ts","../src/commands/convert-ecfr.ts","../src/commands/download-ecfr.ts"],"sourcesContent":["/** lexbuild CLI — Convert U.S. legal XML to structured Markdown */\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { convertUscCommand } from \"./commands/convert-usc.js\";\nimport { downloadUscCommand } from \"./commands/download-usc.js\";\nimport { convertEcfrCommand } from \"./commands/convert-ecfr.js\";\nimport { downloadEcfrCommand } from \"./commands/download-ecfr.js\";\nimport { error } from \"./ui.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"lexbuild\")\n .description(\"Convert U.S. legal XML to structured Markdown for AI/RAG ingestion\")\n .version(pkg.version)\n .addHelpText(\n \"after\",\n `\nQuick start (U.S. Code):\n $ lexbuild download-usc --all Download all 54 USC titles from OLRC\n $ lexbuild convert-usc --all Convert all downloaded USC titles\n $ lexbuild convert-usc --titles 1 Convert USC Title 1 only\n\nQuick start (eCFR):\n $ lexbuild download-ecfr --all Download all 50 eCFR titles from govinfo\n $ lexbuild convert-ecfr --all Convert all downloaded eCFR titles\n $ lexbuild convert-ecfr --titles 17 Convert eCFR Title 17 only\n\nDocumentation: https://github.com/chris-c-thomas/LexBuild`,\n );\n\n// Source-specific commands\nprogram.addCommand(downloadUscCommand);\nprogram.addCommand(convertUscCommand);\nprogram.addCommand(downloadEcfrCommand);\nprogram.addCommand(convertEcfrCommand);\n\n// Bare \"download\" and \"convert\" stubs — guide users to source-specific commands\nprogram.addCommand(\n new Command(\"download\")\n .description(\"(use download-usc or download-ecfr)\")\n .allowUnknownOption()\n .helpOption(false)\n .action(() => {\n console.error(error(\"Please specify a source:\\n\"));\n console.error(\" lexbuild download-usc Download U.S. Code XML from OLRC\");\n console.error(\" lexbuild download-ecfr Download eCFR XML from govinfo\");\n console.error(\"\");\n process.exit(1);\n }),\n);\n\nprogram.addCommand(\n new Command(\"convert\")\n .description(\"(use convert-usc or convert-ecfr)\")\n .allowUnknownOption()\n .helpOption(false)\n .action(() => {\n console.error(error(\"Please specify a source:\\n\"));\n console.error(\" lexbuild convert-usc Convert U.S. Code XML to Markdown\");\n console.error(\" lexbuild convert-ecfr Convert eCFR XML to Markdown\");\n console.error(\"\");\n process.exit(1);\n }),\n);\n\nprogram.parse();\n","/**\n * `lexbuild convert-usc` command — converts USC XML files to Markdown.\n */\n\nimport chalk from \"chalk\";\nimport { Command, Option } from \"commander\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { basename, dirname, join, relative, resolve } from \"node:path\";\nimport { convertTitle } from \"@lexbuild/usc\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n formatNumber,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the convert command */\ninterface ConvertCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n inputDir: string;\n granularity: \"section\" | \"chapter\" | \"title\";\n linkStyle: \"relative\" | \"canonical\" | \"plaintext\";\n includeSourceCredits: boolean;\n includeNotes: boolean;\n includeEditorialNotes: boolean;\n includeStatutoryNotes: boolean;\n includeAmendments: boolean;\n dryRun: boolean;\n verbose: boolean;\n}\n\n/** Build the shared convert options from CLI flags. */\nfunction buildConvertOptions(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n) {\n const hasSelectiveFlags =\n options.includeEditorialNotes || options.includeStatutoryNotes || options.includeAmendments;\n const includeNotes = hasSelectiveFlags ? false : options.includeNotes;\n\n return {\n input: inputPath,\n output: outputPath,\n granularity: options.granularity,\n linkStyle: options.linkStyle,\n includeSourceCredits: options.includeSourceCredits,\n includeNotes,\n includeEditorialNotes: options.includeEditorialNotes,\n includeStatutoryNotes: options.includeStatutoryNotes,\n includeAmendments: options.includeAmendments,\n dryRun: options.dryRun,\n };\n}\n\n/** Resolve the XML file path for a given title number. */\nfunction titleXmlPath(inputDir: string, titleNum: number): string {\n const padded = String(titleNum).padStart(2, \"0\");\n return join(inputDir, `usc${padded}.xml`);\n}\n\n/** Try to resolve a USC XML path, falling back to zero-padded filename. */\nexport function resolveUscXmlPath(inputPath: string): string | undefined {\n if (existsSync(inputPath)) return inputPath;\n\n // Check if filename matches usc{N}.xml pattern and try zero-padded\n const dir = dirname(inputPath);\n const base = basename(inputPath);\n const match = /^usc(\\d+)\\.xml$/.exec(base);\n if (match?.[1]) {\n const padded = match[1].padStart(2, \"0\");\n const paddedPath = join(dir, `usc${padded}.xml`);\n if (existsSync(paddedPath)) return paddedPath;\n }\n\n return undefined;\n}\n\n/** Regex matching USC XML filenames like usc01.xml, usc54.xml */\nconst USC_XML_RE = /^usc(\\d{2})\\.xml$/;\n\n/**\n * Scan a directory for USC XML files and return their title numbers, sorted.\n */\nexport function discoverTitles(inputDir: string): number[] {\n if (!existsSync(inputDir)) return [];\n\n return readdirSync(inputDir)\n .map((name) => USC_XML_RE.exec(name))\n .filter((m): m is RegExpExecArray => m !== null)\n .map((m) => parseInt(m[1] ?? \"0\", 10))\n .sort((a, b) => a - b);\n}\n\n/** Result from runConversion including elapsed time. */\ninterface ConversionRun {\n result: Awaited<ReturnType<typeof convertTitle>>;\n elapsed: number;\n}\n\n/** Run a conversion without printing output. Returns the result and elapsed time. */\nasync function runConversion(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n): Promise<ConversionRun> {\n const startTime = performance.now();\n const result = await convertTitle(buildConvertOptions(inputPath, outputPath, options));\n const elapsed = performance.now() - startTime;\n return { result, elapsed };\n}\n\n/** Convert a single XML file and print its detailed summary. */\nasync function convertSingleFile(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n spinnerLabel: string,\n) {\n const spinner = createSpinner(spinnerLabel);\n spinner.start();\n\n try {\n const { result, elapsed } = await runConversion(inputPath, outputPath, options);\n\n spinner.stop();\n\n const rows: Array<[string, string]> = [];\n if (options.granularity === \"section\") {\n rows.push([\"Sections\", formatNumber(result.sectionsWritten)]);\n rows.push([\"Chapters\", formatNumber(result.chapterCount)]);\n } else if (options.granularity === \"chapter\") {\n rows.push([\"Chapters\", formatNumber(result.chapterCount)]);\n }\n rows.push([\"Est. Tokens\", formatNumber(result.totalTokenEstimate)]);\n\n if (!result.dryRun) {\n rows.push([\"Files Written\", formatNumber(result.files.length)]);\n }\n\n rows.push(\n [\"Peak Memory\", formatBytes(result.peakMemoryBytes)],\n [\"Duration\", formatDuration(elapsed)],\n );\n\n const titleLabel = result.dryRun\n ? `lexbuild — Title ${result.titleNumber}: ${result.titleName} [dry-run]`\n : `lexbuild — Title ${result.titleNumber}: ${result.titleName}`;\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n\n const output = summaryBlock({\n title: titleLabel,\n rows: [...rows, [\"Output\", outputRelative]],\n footer: result.dryRun ? success(\"Dry run complete\") : success(\"Conversion complete\"),\n });\n process.stdout.write(output);\n\n if (options.verbose && !result.dryRun && result.files.length > 0) {\n console.log(\" Files written:\");\n for (const file of result.files) {\n console.log(` ${relative(process.cwd(), file) || file}`);\n }\n console.log(\"\");\n }\n\n return result;\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nexport const convertUscCommand = new Command(\"convert-usc\")\n .description(\"Convert U.S. Code XML file(s) to Markdown\")\n .argument(\"[input]\", \"Path to a USC XML file\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"./output\")\n .option(\"--titles <spec>\", \"Title(s) to convert: 1, 1-5, or 1-5,8,11\")\n .option(\"--all\", \"Convert all downloaded titles found in --input-dir\", false)\n .option(\"-i, --input-dir <dir>\", \"Directory containing USC XML files\", \"./downloads/usc/xml\")\n .addOption(\n new Option(\"-g, --granularity <level>\", \"Output granularity: section, chapter, or title\")\n .choices([\"section\", \"chapter\", \"title\"])\n .default(\"section\"),\n )\n .addOption(\n new Option(\"--link-style <style>\", \"Link style: relative, canonical, or plaintext\")\n .choices([\"relative\", \"canonical\", \"plaintext\"])\n .default(\"plaintext\"),\n )\n .option(\"--include-source-credits\", \"Include source credit annotations\", true)\n .option(\"--no-include-source-credits\", \"Exclude source credit annotations\")\n .option(\"--include-notes\", \"Include all notes (default)\", true)\n .option(\"--no-include-notes\", \"Exclude all notes\")\n .option(\"--include-editorial-notes\", \"Include editorial notes only\", false)\n .option(\"--include-statutory-notes\", \"Include statutory notes only\", false)\n .option(\"--include-amendments\", \"Include amendment history notes only\", false)\n .option(\"--dry-run\", \"Parse and report structure without writing files\", false)\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .addHelpText(\n \"after\",\n `\nInput modes (use exactly one):\n <input> Convert a single XML file\n --titles Convert specific titles by number\n --all Convert all titles in --input-dir\n\nGranularity:\n section One .md file per section (default)\n chapter One .md file per chapter, sections inlined\n title One .md file per title, entire hierarchy inlined\n\nExamples:\n $ lexbuild convert-usc --titles 1 Convert Title 1\n $ lexbuild convert-usc --titles 1-5,8,11 Convert a mix of titles\n $ lexbuild convert-usc --all -g chapter All titles, chapter-level\n $ lexbuild convert-usc --titles 26 -g title Title 26 as a single file\n $ lexbuild convert-usc --all --dry-run Preview stats only\n $ lexbuild convert-usc ./downloads/usc/xml/usc01.xml -o ./out`,\n )\n .action(async (input: string | undefined, options: ConvertCommandOptions) => {\n // Validate: must specify exactly one of <input>, --titles, or --all\n const modeCount = [input, options.titles, options.all].filter(Boolean).length;\n if (modeCount === 0) {\n console.error(\n error(\"Specify an input file, --titles <spec>, or --all (e.g. --titles 1-5,8,11)\"),\n );\n process.exit(1);\n }\n if (modeCount > 1) {\n console.error(error(\"Cannot combine <input>, --titles, and --all — use only one\"));\n process.exit(1);\n }\n\n const outputPath = resolve(options.output);\n const dryRunLabel = options.dryRun ? \" [dry-run]\" : \"\";\n\n // Single-file mode\n if (input) {\n const rawPath = resolve(input);\n const inputPath = resolveUscXmlPath(rawPath);\n if (!inputPath) {\n console.error(error(`Input file not found: ${rawPath}`));\n process.exit(1);\n }\n await convertSingleFile(inputPath, outputPath, options, `Converting${dryRunLabel}...`);\n return;\n }\n\n // Multi-title mode\n let titles: number[];\n if (options.all) {\n const inputDir = resolve(options.inputDir);\n titles = discoverTitles(inputDir);\n if (titles.length === 0) {\n console.error(error(`No USC XML files found in ${inputDir}`));\n process.exit(1);\n }\n } else {\n const titlesSpec = options.titles as string;\n try {\n titles = parseTitles(titlesSpec);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const inputDir = resolve(options.inputDir);\n const totalTitles = titles.length;\n const results: ConversionRun[] = [];\n\n const spinner = createSpinner(`Converting${dryRunLabel}...`);\n spinner.start();\n\n for (const [i, titleNum] of titles.entries()) {\n const xmlPath = titleXmlPath(inputDir, titleNum);\n\n if (!existsSync(xmlPath)) {\n spinner.stop();\n console.error(error(`XML file not found: ${xmlPath}`));\n process.exit(1);\n }\n\n spinner.text = `Converting Title ${titleNum}${dryRunLabel} (${i + 1}/${totalTitles})...`;\n\n try {\n const run = await runConversion(xmlPath, outputPath, options);\n results.push(run);\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n spinner.stop();\n\n // Summary header\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n const headerTitle = options.dryRun\n ? \"lexbuild — Conversion Summary [dry-run]\"\n : \"lexbuild — Conversion Summary\";\n const header = summaryBlock({\n title: headerTitle,\n rows: [[\"Directory\", outputRelative]],\n });\n process.stdout.write(header);\n\n // Build data table rows — adapt columns to granularity\n const granularity = options.granularity;\n let totalSections = 0;\n let totalChapters = 0;\n let totalTokens = 0;\n let totalElapsed = 0;\n\n const tableRows = results.map(({ result, elapsed }) => {\n totalSections += result.sectionsWritten;\n totalChapters += result.chapterCount;\n totalTokens += result.totalTokenEstimate;\n totalElapsed += elapsed;\n\n if (granularity === \"title\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"chapter\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.chapterCount),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.chapterCount),\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n }\n });\n\n // Totals row\n if (granularity === \"title\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"chapter\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalChapters)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalChapters)),\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n }\n\n // Table headers and footer — adapt to granularity\n const tableHeaders =\n granularity === \"title\"\n ? [\"Title\", \"Name\", \"Tokens\", \"Duration\"]\n : granularity === \"chapter\"\n ? [\"Title\", \"Name\", \"Chapters\", \"Tokens\", \"Duration\"]\n : [\"Title\", \"Name\", \"Chapters\", \"Sections\", \"Tokens\", \"Duration\"];\n\n console.log(dataTable(tableHeaders, tableRows));\n\n // Footer — show the primary unit for the chosen granularity\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n let countLabel: string;\n if (granularity === \"title\") {\n countLabel = `${totalTitles} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalChapters === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalChapters)} ${chapterWord})`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;\n }\n console.log(`\\n ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);\n console.log(\"\");\n });\n","/**\n * Shared terminal UI utilities — spinners, tables, and formatting helpers.\n */\n\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { Ora } from \"ora\";\nimport Table from \"cli-table3\";\n\n/** Create an ora spinner with consistent styling. */\nexport function createSpinner(text: string): Ora {\n return ora({ text, spinner: \"dots\" });\n}\n\n/** Format milliseconds as human-readable duration (e.g. \"1.5s\" or \"1m 23s\"). */\nexport function formatDuration(ms: number): string {\n const secs = ms / 1000;\n if (secs < 60) {\n return `${secs.toFixed(secs < 10 ? 2 : 1)}s`;\n }\n const mins = Math.floor(secs / 60);\n const remainSecs = Math.round(secs % 60);\n return `${mins}m ${remainSecs}s`;\n}\n\n/** Format bytes as human-readable size (e.g. \"11.0 MB\"). */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const kb = bytes / 1024;\n if (kb < 1024) return `${kb.toFixed(1)} KB`;\n const mb = kb / 1024;\n if (mb < 1024) return `${mb.toFixed(1)} MB`;\n const gb = mb / 1024;\n return `${gb.toFixed(2)} GB`;\n}\n\n/** Format a number with locale-aware separators (e.g. 1,234,567). */\nexport function formatNumber(n: number): string {\n return n.toLocaleString();\n}\n\n/** Regex matching ANSI escape sequences (SGR codes). */\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1B\\[[0-9;]*m/g;\n\n/** Strip ANSI escape codes and return visual string length. */\nfunction visualLength(s: string): number {\n return s.replace(ANSI_RE, \"\").length;\n}\n\n/**\n * Compute column widths that fill the terminal.\n * Expands `flexCol` to absorb remaining space.\n */\nfunction fillWidths(columns: string[][], colCount: number, flexCol: number): number[] | undefined {\n const termWidth = process.stdout.columns || 80;\n // Compute natural width per column (max visual length across all rows)\n const natural = Array.from<number>({ length: colCount }).fill(0);\n for (const row of columns) {\n for (let i = 0; i < colCount; i++) {\n natural[i] = Math.max(natural[i] ?? 0, visualLength(row[i] ?? \"\"));\n }\n }\n // Overhead: 2 chars left pad + 2 chars per column separator (between cols)\n const overhead = 2 + 2 * (colCount - 1);\n const naturalTotal = overhead + natural.reduce((a, b) => a + b, 0);\n if (naturalTotal >= termWidth) return undefined; // already fills or exceeds\n const extra = termWidth - naturalTotal;\n const widths = [...natural];\n widths[flexCol] = (widths[flexCol] ?? 0) + extra;\n return widths;\n}\n\n/**\n * Shared cli-table3 border characters.\n *\n * Uses 2-char left indent (\" \"), 2-char column gap (\" \"), no right border.\n * The \"mid\" intersection chars (\"──\") must match the 2-char width of \"middle\"\n * so horizontal rules span the full table width.\n */\nconst TABLE_CHARS = {\n top: \"─\",\n \"top-mid\": \"──\",\n \"top-left\": \" \",\n \"top-right\": \"\",\n bottom: \"─\",\n \"bottom-mid\": \"──\",\n \"bottom-left\": \" \",\n \"bottom-right\": \"\",\n left: \" \",\n \"left-mid\": \" \",\n right: \"\",\n \"right-mid\": \"\",\n mid: \"─\",\n \"mid-mid\": \"──\",\n middle: \" \",\n} as const;\n\n/** Shared cli-table3 style. */\nconst TABLE_STYLE: {\n head: string[];\n border: string[];\n \"padding-left\": number;\n \"padding-right\": number;\n} = {\n head: [],\n border: [],\n \"padding-left\": 0,\n \"padding-right\": 0,\n};\n\n/** Render a styled heading line. */\nexport function heading(text: string): string {\n return chalk.bold(text);\n}\n\n/** Render a success message with green checkmark. */\nexport function success(text: string): string {\n return chalk.green(`✔ ${text}`);\n}\n\n/** Render an error message with red X. */\nexport function error(text: string): string {\n return chalk.red(`✘ ${text}`);\n}\n\n/** Options for a key-value summary block. */\ninterface SummaryBlockOptions {\n /** Title line displayed above the table */\n title: string;\n /** Key-value pairs to display */\n rows: Array<[label: string, value: string]>;\n /** Optional footer line displayed below the table */\n footer?: string | undefined;\n}\n\n/** Render a key-value summary block with horizontal rules. */\nexport function summaryBlock({ title, rows, footer }: SummaryBlockOptions): string {\n const allCells = rows.map(([l, v]) => [l, v]);\n const colWidths = fillWidths(allCells, 2, 1);\n\n const table = new Table({\n chars: TABLE_CHARS,\n style: TABLE_STYLE,\n ...(colWidths ? { colWidths } : {}),\n });\n\n for (const [label, value] of rows) {\n table.push([chalk.dim(label), value]);\n }\n\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(` ${heading(title)}`);\n lines.push(table.toString());\n if (footer !== undefined) {\n lines.push(` ${footer}`);\n }\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n/** Render a multi-column data table. */\nexport function dataTable(head: string[], rows: string[][]): string {\n const table = new Table({\n head: head.map((h) => chalk.dim(h)),\n chars: TABLE_CHARS,\n style: TABLE_STYLE,\n });\n\n for (const row of rows) {\n table.push(row);\n }\n\n return table.toString();\n}\n","/**\n * Parses a title specification string into an array of title numbers.\n *\n * Supports single numbers, comma-separated lists, ranges, and mixed:\n * - `\"29\"` → `[29]`\n * - `\"1,3,8,11\"` → `[1, 3, 8, 11]`\n * - `\"1-5\"` → `[1, 2, 3, 4, 5]`\n * - `\"1-5,8,11\"` → `[1, 2, 3, 4, 5, 8, 11]`\n */\nexport function parseTitles(input: string, maxTitle = 54): number[] {\n const trimmed = input.trim();\n if (trimmed === \"\") {\n throw new Error(\"Title specification cannot be empty\");\n }\n\n const result = new Set<number>();\n const segments = trimmed.split(\",\");\n\n for (const segment of segments) {\n const part = segment.trim();\n if (part === \"\") {\n throw new Error(`Invalid title specification: \"${input}\" (empty segment)`);\n }\n\n if (part.includes(\"-\")) {\n const [startStr, endStr, ...rest] = part.split(\"-\");\n if (rest.length > 0 || !startStr || !endStr) {\n throw new Error(`Invalid range: \"${part}\" (expected format: start-end)`);\n }\n\n const start = parseIntStrict(startStr, input);\n const end = parseIntStrict(endStr, input);\n\n if (start > end) {\n throw new Error(`Invalid range: \"${part}\" (start ${start} must be ≤ end ${end})`);\n }\n\n validateTitleNumber(start, input, maxTitle);\n validateTitleNumber(end, input, maxTitle);\n\n for (let i = start; i <= end; i++) {\n result.add(i);\n }\n } else {\n const num = parseIntStrict(part, input);\n validateTitleNumber(num, input, maxTitle);\n result.add(num);\n }\n }\n\n return [...result].sort((a, b) => a - b);\n}\n\n/** Parse an integer strictly — no NaN, no floats. */\nfunction parseIntStrict(str: string, fullInput: string): number {\n const trimmed = str.trim();\n if (!/^\\d+$/.test(trimmed)) {\n throw new Error(`Invalid number \"${trimmed}\" in title specification: \"${fullInput}\"`);\n }\n return parseInt(trimmed, 10);\n}\n\n/** Validate that a title number is in the valid range. */\nfunction validateTitleNumber(num: number, fullInput: string, max = 54): void {\n if (num < 1 || num > max) {\n throw new Error(`Title number ${num} out of range (must be 1-${max}) in: \"${fullInput}\"`);\n }\n}\n","/**\n * `lexbuild download-usc` command — downloads USC XML from OLRC.\n */\n\nimport { Command } from \"commander\";\nimport { relative, resolve } from \"node:path\";\nimport { downloadTitles, CURRENT_RELEASE_POINT } from \"@lexbuild/usc\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the download command */\ninterface DownloadCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n releasePoint: string;\n}\n\nexport const downloadUscCommand = new Command(\"download-usc\")\n .description(\"Download U.S. Code XML from OLRC\")\n .option(\"-o, --output <dir>\", \"Download directory\", \"./downloads/usc/xml\")\n .option(\"--titles <spec>\", \"Title(s) to download: 1, 1-5, or 1-5,8,11\")\n .option(\"--all\", \"Download all 54 titles (single bulk zip)\", false)\n .option(\"--release-point <id>\", \"OLRC release point identifier\", CURRENT_RELEASE_POINT)\n .addHelpText(\n \"after\",\n `\nExamples:\n $ lexbuild download-usc --all Download all 54 titles\n $ lexbuild download-usc --titles 1 Download Title 1 only\n $ lexbuild download-usc --titles 1-5,8,11 Download specific titles\n $ lexbuild download-usc --all -o ./my-xml Custom output directory\n\nSource: https://uscode.house.gov/download/download.shtml`,\n )\n .action(async (options: DownloadCommandOptions) => {\n // Validate: must specify --titles or --all\n if (!options.titles && !options.all) {\n console.error(error(\"Specify --titles <spec> or --all (e.g. --titles 1-5,8,11)\"));\n process.exit(1);\n }\n\n // Parse title numbers\n let titles: number[] | undefined;\n if (options.titles) {\n try {\n titles = parseTitles(options.titles);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const outputDir = resolve(options.output);\n const titleCount = titles ? titles.length : 54;\n const label =\n titleCount === 1 ? `Downloading Title ${titles?.[0]}` : `Downloading ${titleCount} titles`;\n\n const spinner = createSpinner(`${label}...`);\n spinner.start();\n\n const startTime = performance.now();\n\n try {\n const result = await downloadTitles({\n outputDir,\n titles,\n releasePoint: options.releasePoint,\n });\n\n const elapsed = performance.now() - startTime;\n\n spinner.stop();\n\n // Build file table rows\n const fileRows = result.files.map((file) => [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.filePath) || file.filePath,\n ]);\n\n const totalBytes = result.files.reduce((sum, f) => sum + f.size, 0);\n\n // Summary header\n const output = summaryBlock({\n title: \"lexbuild — Download Summary\",\n rows: [\n [\"Release Point\", options.releasePoint],\n [\"Directory\", relative(process.cwd(), outputDir) || outputDir],\n ],\n });\n process.stdout.write(output);\n\n // File table\n if (fileRows.length > 0) {\n console.log(dataTable([\"Title\", \"Size\", \"File\"], fileRows));\n }\n\n // Errors\n if (result.errors.length > 0) {\n console.log(\"\");\n for (const err of result.errors) {\n console.log(` ${error(`Title ${err.titleNumber}: ${err.message}`)}`);\n }\n }\n\n // Footer\n const titleWord = result.files.length === 1 ? \"title\" : \"titles\";\n const summary = `Downloaded ${result.files.length} ${titleWord} (${formatBytes(totalBytes)}) in ${formatDuration(elapsed)}`;\n const failSuffix = result.errors.length > 0 ? ` (${result.errors.length} failed)` : \"\";\n console.log(` ${success(summary + failSuffix)}`);\n console.log(\"\");\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n","/**\n * `lexbuild convert-ecfr` command — converts eCFR XML files to Markdown.\n */\n\nimport chalk from \"chalk\";\nimport { Command, Option } from \"commander\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { join, relative, resolve } from \"node:path\";\nimport { convertEcfrTitle } from \"@lexbuild/ecfr\";\nimport type { EcfrConvertOptions, EcfrConvertResult } from \"@lexbuild/ecfr\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n formatNumber,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the convert-ecfr command */\ninterface ConvertEcfrCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n inputDir: string;\n granularity: \"section\" | \"part\" | \"chapter\" | \"title\";\n linkStyle: \"relative\" | \"canonical\" | \"plaintext\";\n includeSourceCredits: boolean;\n includeNotes: boolean;\n includeEditorialNotes: boolean;\n includeStatutoryNotes: boolean;\n includeAmendments: boolean;\n dryRun: boolean;\n verbose: boolean;\n}\n\n/** Build EcfrConvertOptions from CLI flags. */\nfunction buildConvertOptions(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n): EcfrConvertOptions {\n const hasSelectiveFlags =\n options.includeEditorialNotes || options.includeStatutoryNotes || options.includeAmendments;\n const includeNotes = hasSelectiveFlags ? false : options.includeNotes;\n\n return {\n input: inputPath,\n output: outputPath,\n granularity: options.granularity,\n linkStyle: options.linkStyle,\n includeSourceCredits: options.includeSourceCredits,\n includeNotes,\n includeEditorialNotes: options.includeEditorialNotes,\n includeStatutoryNotes: options.includeStatutoryNotes,\n includeAmendments: options.includeAmendments,\n dryRun: options.dryRun,\n };\n}\n\n/** Regex matching eCFR XML filenames like ECFR-title1.xml, ECFR-title17.xml */\nconst ECFR_XML_RE = /^ECFR-title(\\d+)\\.xml$/;\n\n/** Resolve the XML file path for a given eCFR title number. */\nfunction titleXmlPath(inputDir: string, titleNum: number): string {\n return join(inputDir, `ECFR-title${titleNum}.xml`);\n}\n\n/** Scan a directory for eCFR XML files and return their title numbers, sorted. */\nfunction discoverEcfrTitles(inputDir: string): number[] {\n if (!existsSync(inputDir)) return [];\n\n return readdirSync(inputDir)\n .map((name) => ECFR_XML_RE.exec(name))\n .filter((m): m is RegExpExecArray => m !== null)\n .map((m) => parseInt(m[1] ?? \"0\", 10))\n .sort((a, b) => a - b);\n}\n\n/** Result from running a conversion including elapsed time. */\ninterface ConversionRun {\n result: EcfrConvertResult;\n elapsed: number;\n}\n\n/** Run a conversion without printing output. */\nasync function runConversion(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n): Promise<ConversionRun> {\n const startTime = performance.now();\n const result = await convertEcfrTitle(buildConvertOptions(inputPath, outputPath, options));\n const elapsed = performance.now() - startTime;\n return { result, elapsed };\n}\n\n/** Convert a single file and print its detailed summary. */\nasync function convertSingleFile(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n spinnerLabel: string,\n) {\n const spinner = createSpinner(spinnerLabel);\n spinner.start();\n\n try {\n const { result, elapsed } = await runConversion(inputPath, outputPath, options);\n spinner.stop();\n\n const rows: Array<[string, string]> = [];\n if (options.granularity === \"section\") {\n rows.push([\"Sections\", formatNumber(result.sectionsWritten)]);\n rows.push([\"Parts\", formatNumber(result.partCount)]);\n } else if (options.granularity === \"part\") {\n rows.push([\"Parts\", formatNumber(result.partCount)]);\n } else if (options.granularity === \"chapter\") {\n rows.push([\"Chapters\", formatNumber(result.sectionsWritten)]);\n }\n rows.push([\"Est. Tokens\", formatNumber(result.totalTokenEstimate)]);\n\n if (!result.dryRun) {\n rows.push([\"Files Written\", formatNumber(result.files.length)]);\n }\n\n rows.push(\n [\"Peak Memory\", formatBytes(result.peakMemoryBytes)],\n [\"Duration\", formatDuration(elapsed)],\n );\n\n const titleLabel = result.dryRun\n ? `lexbuild — eCFR Title ${result.titleNumber}: ${result.titleName} [dry-run]`\n : `lexbuild — eCFR Title ${result.titleNumber}: ${result.titleName}`;\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n\n const output = summaryBlock({\n title: titleLabel,\n rows: [...rows, [\"Output\", outputRelative]],\n footer: result.dryRun ? success(\"Dry run complete\") : success(\"Conversion complete\"),\n });\n process.stdout.write(output);\n\n if (options.verbose && !result.dryRun && result.files.length > 0) {\n console.log(\" Files written:\");\n for (const file of result.files) {\n console.log(` ${relative(process.cwd(), file) || file}`);\n }\n console.log(\"\");\n }\n\n return result;\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nexport const convertEcfrCommand = new Command(\"convert-ecfr\")\n .description(\"Convert eCFR XML file(s) to Markdown\")\n .argument(\"[input]\", \"Path to an eCFR XML file\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"./output\")\n .option(\"--titles <spec>\", \"Title(s) to convert: 1, 1-5, or 1-5,17\")\n .option(\"--all\", \"Convert all downloaded eCFR titles found in --input-dir\", false)\n .option(\"-i, --input-dir <dir>\", \"Directory containing eCFR XML files\", \"./downloads/ecfr/xml\")\n .addOption(\n new Option(\"-g, --granularity <level>\", \"Output granularity: section, part, chapter, or title\")\n .choices([\"section\", \"part\", \"chapter\", \"title\"])\n .default(\"section\"),\n )\n .addOption(\n new Option(\"--link-style <style>\", \"Link style: relative, canonical, or plaintext\")\n .choices([\"relative\", \"canonical\", \"plaintext\"])\n .default(\"plaintext\"),\n )\n .option(\"--include-source-credits\", \"Include source credit annotations\", true)\n .option(\"--no-include-source-credits\", \"Exclude source credit annotations\")\n .option(\"--include-notes\", \"Include all notes (default)\", true)\n .option(\"--no-include-notes\", \"Exclude all notes\")\n .option(\"--include-editorial-notes\", \"Include editorial notes only\", false)\n .option(\"--include-statutory-notes\", \"Include statutory/regulatory notes only\", false)\n .option(\"--include-amendments\", \"Include amendment history notes only\", false)\n .option(\"--dry-run\", \"Parse and report structure without writing files\", false)\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .addHelpText(\n \"after\",\n `\nInput modes (use exactly one):\n <input> Convert a single eCFR XML file\n --titles Convert specific titles by number\n --all Convert all titles in --input-dir\n\nGranularity:\n section One .md file per section (default)\n part One .md file per part, sections inlined\n chapter One .md file per chapter, parts and sections inlined\n title One .md file per title, entire hierarchy inlined\n\nExamples:\n $ lexbuild convert-ecfr --titles 1 Convert eCFR Title 1\n $ lexbuild convert-ecfr --titles 1-5,17 Convert specific titles\n $ lexbuild convert-ecfr --all -g part All titles, part-level\n $ lexbuild convert-ecfr --all --dry-run Preview stats only\n $ lexbuild convert-ecfr ./downloads/ecfr/xml/ECFR-title1.xml -o ./out`,\n )\n .action(async (input: string | undefined, options: ConvertEcfrCommandOptions) => {\n const modeCount = [input, options.titles, options.all].filter(Boolean).length;\n if (modeCount === 0) {\n console.error(\n error(\"Specify an input file, --titles <spec>, or --all (e.g. --titles 1-5,17)\"),\n );\n process.exit(1);\n }\n if (modeCount > 1) {\n console.error(error(\"Cannot combine <input>, --titles, and --all — use only one\"));\n process.exit(1);\n }\n\n const outputPath = resolve(options.output);\n const dryRunLabel = options.dryRun ? \" [dry-run]\" : \"\";\n\n // Single-file mode\n if (input) {\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n console.error(error(`Input file not found: ${inputPath}`));\n process.exit(1);\n }\n await convertSingleFile(inputPath, outputPath, options, `Converting eCFR${dryRunLabel}...`);\n return;\n }\n\n // Multi-title mode\n let titles: number[];\n if (options.all) {\n const inputDir = resolve(options.inputDir);\n titles = discoverEcfrTitles(inputDir);\n if (titles.length === 0) {\n console.error(error(`No eCFR XML files found in ${inputDir}`));\n process.exit(1);\n }\n } else {\n const titlesSpec = options.titles as string;\n try {\n titles = parseTitles(titlesSpec, 50);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const inputDir = resolve(options.inputDir);\n const totalTitles = titles.length;\n const results: ConversionRun[] = [];\n\n const spinner = createSpinner(`Converting eCFR${dryRunLabel}...`);\n spinner.start();\n\n for (const [i, titleNum] of titles.entries()) {\n const xmlPath = titleXmlPath(inputDir, titleNum);\n\n if (!existsSync(xmlPath)) {\n spinner.stop();\n console.error(error(`XML file not found: ${xmlPath}`));\n process.exit(1);\n }\n\n spinner.text = `Converting eCFR Title ${titleNum}${dryRunLabel} (${i + 1}/${totalTitles})...`;\n\n try {\n const run = await runConversion(xmlPath, outputPath, options);\n results.push(run);\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n spinner.stop();\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n const headerTitle = options.dryRun\n ? \"lexbuild — eCFR Conversion Summary [dry-run]\"\n : \"lexbuild — eCFR Conversion Summary\";\n const header = summaryBlock({\n title: headerTitle,\n rows: [[\"Directory\", outputRelative]],\n });\n process.stdout.write(header);\n\n const granularity = options.granularity;\n let totalSections = 0;\n let totalParts = 0;\n let totalTokens = 0;\n let totalElapsed = 0;\n\n const tableRows = results.map(({ result, elapsed }) => {\n totalSections += result.sectionsWritten;\n totalParts += result.partCount;\n totalTokens += result.totalTokenEstimate;\n totalElapsed += elapsed;\n\n if (granularity === \"title\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"chapter\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"part\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.partCount),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.partCount),\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n }\n });\n\n if (granularity === \"title\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"chapter\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"part\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalParts)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalParts)),\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n }\n\n const tableHeaders =\n granularity === \"title\"\n ? [\"Title\", \"Name\", \"Tokens\", \"Duration\"]\n : granularity === \"chapter\"\n ? [\"Title\", \"Name\", \"Chapters\", \"Tokens\", \"Duration\"]\n : granularity === \"part\"\n ? [\"Title\", \"Name\", \"Parts\", \"Tokens\", \"Duration\"]\n : [\"Title\", \"Name\", \"Parts\", \"Sections\", \"Tokens\", \"Duration\"];\n\n console.log(dataTable(tableHeaders, tableRows));\n\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n let countLabel: string;\n if (granularity === \"title\") {\n countLabel = `${totalTitles} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalSections === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${chapterWord})`;\n } else if (granularity === \"part\") {\n const partWord = totalParts === 1 ? \"part\" : \"parts\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalParts)} ${partWord})`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;\n }\n console.log(`\\n ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);\n console.log(\"\");\n });\n","/**\n * `lexbuild download-ecfr` command — downloads eCFR XML from govinfo.\n */\n\nimport { Command } from \"commander\";\nimport { relative, resolve } from \"node:path\";\nimport { downloadEcfrTitles } from \"@lexbuild/ecfr\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the download-ecfr command */\ninterface DownloadEcfrOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n}\n\nexport const downloadEcfrCommand = new Command(\"download-ecfr\")\n .description(\"Download eCFR XML from govinfo bulk data repository\")\n .option(\"-o, --output <dir>\", \"Download directory\", \"./downloads/ecfr/xml\")\n .option(\"--titles <spec>\", \"Title(s) to download: 1, 1-5, or 1-5,8,17\")\n .option(\"--all\", \"Download all 50 eCFR titles\", false)\n .addHelpText(\n \"after\",\n `\nExamples:\n $ lexbuild download-ecfr --all Download all 50 titles\n $ lexbuild download-ecfr --titles 1 Download Title 1 only\n $ lexbuild download-ecfr --titles 1-5,17 Download specific titles\n $ lexbuild download-ecfr --all -o ./my-xml Custom output directory\n\nSource: https://www.govinfo.gov/bulkdata/ECFR`,\n )\n .action(async (options: DownloadEcfrOptions) => {\n if (!options.titles && !options.all) {\n console.error(error(\"Specify --titles <spec> or --all (e.g. --titles 1-5,17)\"));\n process.exit(1);\n }\n\n let titles: number[] | undefined;\n if (options.titles) {\n try {\n titles = parseTitles(options.titles, 50);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const outputDir = resolve(options.output);\n const titleCount = titles ? titles.length : 50;\n const label =\n titleCount === 1\n ? `Downloading eCFR Title ${titles?.[0]}`\n : `Downloading ${titleCount} eCFR titles`;\n\n const spinner = createSpinner(`${label}...`);\n spinner.start();\n\n const startTime = performance.now();\n\n try {\n const result = await downloadEcfrTitles({\n output: outputDir,\n titles,\n });\n\n const elapsed = performance.now() - startTime;\n spinner.stop();\n\n const fileRows = result.files.map((file) => [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.path) || file.path,\n ]);\n\n const output = summaryBlock({\n title: \"lexbuild — eCFR Download Summary\",\n rows: [\n [\"Source\", \"govinfo.gov/bulkdata/ECFR\"],\n [\"Directory\", relative(process.cwd(), outputDir) || outputDir],\n ],\n });\n process.stdout.write(output);\n\n if (fileRows.length > 0) {\n console.log(dataTable([\"Title\", \"Size\", \"File\"], fileRows));\n }\n\n const titleWord = result.titlesDownloaded === 1 ? \"title\" : \"titles\";\n const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;\n console.log(` ${success(summary)}`);\n console.log(\"\");\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n"],"mappings":"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","names":["Command","chalk","inputDir","chalk","Command","relative","resolve","Command","resolve","relative","chalk","Command","Option","existsSync","readdirSync","join","relative","resolve","buildConvertOptions","titleXmlPath","join","existsSync","readdirSync","runConversion","convertSingleFile","relative","Command","Option","resolve","inputDir","chalk","Command","relative","resolve","Command","resolve","relative","require","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/convert-usc.ts","../src/ui.ts","../src/parse-titles.ts","../src/commands/download-usc.ts","../src/commands/convert-ecfr.ts","../src/commands/download-ecfr.ts"],"sourcesContent":["/** lexbuild CLI — Convert U.S. legal XML to structured Markdown */\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { convertUscCommand } from \"./commands/convert-usc.js\";\nimport { downloadUscCommand } from \"./commands/download-usc.js\";\nimport { convertEcfrCommand } from \"./commands/convert-ecfr.js\";\nimport { downloadEcfrCommand } from \"./commands/download-ecfr.js\";\nimport { error } from \"./ui.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"lexbuild\")\n .description(\"Convert U.S. legal XML to structured Markdown for AI/RAG ingestion\")\n .version(pkg.version)\n .addHelpText(\n \"after\",\n `\nQuick start (U.S. Code):\n $ lexbuild download-usc --all Download all 54 USC titles from OLRC\n $ lexbuild convert-usc --all Convert all downloaded USC titles\n $ lexbuild convert-usc --titles 1 Convert USC Title 1 only\n\nQuick start (eCFR):\n $ lexbuild download-ecfr --all Download all 50 eCFR titles from govinfo\n $ lexbuild convert-ecfr --all Convert all downloaded eCFR titles\n $ lexbuild convert-ecfr --titles 17 Convert eCFR Title 17 only\n\nDocumentation: https://github.com/chris-c-thomas/LexBuild`,\n );\n\n// Source-specific commands\nprogram.addCommand(downloadUscCommand);\nprogram.addCommand(convertUscCommand);\nprogram.addCommand(downloadEcfrCommand);\nprogram.addCommand(convertEcfrCommand);\n\n// Bare \"download\" and \"convert\" stubs — guide users to source-specific commands\nprogram.addCommand(\n new Command(\"download\")\n .description(\"(use download-usc or download-ecfr)\")\n .allowUnknownOption()\n .helpOption(false)\n .action(() => {\n console.error(error(\"Please specify a source:\\n\"));\n console.error(\" lexbuild download-usc Download U.S. Code XML from OLRC\");\n console.error(\" lexbuild download-ecfr Download eCFR XML from govinfo\");\n console.error(\"\");\n process.exit(1);\n }),\n);\n\nprogram.addCommand(\n new Command(\"convert\")\n .description(\"(use convert-usc or convert-ecfr)\")\n .allowUnknownOption()\n .helpOption(false)\n .action(() => {\n console.error(error(\"Please specify a source:\\n\"));\n console.error(\" lexbuild convert-usc Convert U.S. Code XML to Markdown\");\n console.error(\" lexbuild convert-ecfr Convert eCFR XML to Markdown\");\n console.error(\"\");\n process.exit(1);\n }),\n);\n\nprogram.parse();\n","/**\n * `lexbuild convert-usc` command — converts USC XML files to Markdown.\n */\n\nimport chalk from \"chalk\";\nimport { Command, Option } from \"commander\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { basename, dirname, join, relative, resolve } from \"node:path\";\nimport { convertTitle } from \"@lexbuild/usc\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n formatNumber,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the convert command */\ninterface ConvertCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n inputDir: string;\n granularity: \"section\" | \"chapter\" | \"title\";\n linkStyle: \"relative\" | \"canonical\" | \"plaintext\";\n includeSourceCredits: boolean;\n includeNotes: boolean;\n includeEditorialNotes: boolean;\n includeStatutoryNotes: boolean;\n includeAmendments: boolean;\n dryRun: boolean;\n verbose: boolean;\n}\n\n/** Build the shared convert options from CLI flags. */\nfunction buildConvertOptions(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n) {\n const hasSelectiveFlags =\n options.includeEditorialNotes || options.includeStatutoryNotes || options.includeAmendments;\n const includeNotes = hasSelectiveFlags ? false : options.includeNotes;\n\n return {\n input: inputPath,\n output: outputPath,\n granularity: options.granularity,\n linkStyle: options.linkStyle,\n includeSourceCredits: options.includeSourceCredits,\n includeNotes,\n includeEditorialNotes: options.includeEditorialNotes,\n includeStatutoryNotes: options.includeStatutoryNotes,\n includeAmendments: options.includeAmendments,\n dryRun: options.dryRun,\n };\n}\n\n/** Resolve the XML file path for a given title number. */\nfunction titleXmlPath(inputDir: string, titleNum: number): string {\n const padded = String(titleNum).padStart(2, \"0\");\n return join(inputDir, `usc${padded}.xml`);\n}\n\n/** Try to resolve a USC XML path, falling back to zero-padded filename. */\nexport function resolveUscXmlPath(inputPath: string): string | undefined {\n if (existsSync(inputPath)) return inputPath;\n\n // Check if filename matches usc{N}.xml pattern and try zero-padded\n const dir = dirname(inputPath);\n const base = basename(inputPath);\n const match = /^usc(\\d+)\\.xml$/.exec(base);\n if (match?.[1]) {\n const padded = match[1].padStart(2, \"0\");\n const paddedPath = join(dir, `usc${padded}.xml`);\n if (existsSync(paddedPath)) return paddedPath;\n }\n\n return undefined;\n}\n\n/** Regex matching USC XML filenames like usc01.xml, usc54.xml */\nconst USC_XML_RE = /^usc(\\d{2})\\.xml$/;\n\n/**\n * Scan a directory for USC XML files and return their title numbers, sorted.\n */\nexport function discoverTitles(inputDir: string): number[] {\n if (!existsSync(inputDir)) return [];\n\n return readdirSync(inputDir)\n .map((name) => USC_XML_RE.exec(name))\n .filter((m): m is RegExpExecArray => m !== null)\n .map((m) => parseInt(m[1] ?? \"0\", 10))\n .sort((a, b) => a - b);\n}\n\n/** Result from runConversion including elapsed time. */\ninterface ConversionRun {\n result: Awaited<ReturnType<typeof convertTitle>>;\n elapsed: number;\n}\n\n/** Run a conversion without printing output. Returns the result and elapsed time. */\nasync function runConversion(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n): Promise<ConversionRun> {\n const startTime = performance.now();\n const result = await convertTitle(buildConvertOptions(inputPath, outputPath, options));\n const elapsed = performance.now() - startTime;\n return { result, elapsed };\n}\n\n/** Convert a single XML file and print its detailed summary. */\nasync function convertSingleFile(\n inputPath: string,\n outputPath: string,\n options: ConvertCommandOptions,\n spinnerLabel: string,\n) {\n const spinner = createSpinner(spinnerLabel);\n spinner.start();\n\n try {\n const { result, elapsed } = await runConversion(inputPath, outputPath, options);\n\n spinner.stop();\n\n const rows: Array<[string, string]> = [];\n if (options.granularity === \"section\") {\n rows.push([\"Sections\", formatNumber(result.sectionsWritten)]);\n rows.push([\"Chapters\", formatNumber(result.chapterCount)]);\n } else if (options.granularity === \"chapter\") {\n rows.push([\"Chapters\", formatNumber(result.chapterCount)]);\n }\n rows.push([\"Est. Tokens\", formatNumber(result.totalTokenEstimate)]);\n\n if (!result.dryRun) {\n rows.push([\"Files Written\", formatNumber(result.files.length)]);\n }\n\n rows.push(\n [\"Peak Memory\", formatBytes(result.peakMemoryBytes)],\n [\"Duration\", formatDuration(elapsed)],\n );\n\n const titleLabel = result.dryRun\n ? `lexbuild — Title ${result.titleNumber}: ${result.titleName} [dry-run]`\n : `lexbuild — Title ${result.titleNumber}: ${result.titleName}`;\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n\n const output = summaryBlock({\n title: titleLabel,\n rows: [...rows, [\"Output\", outputRelative]],\n footer: result.dryRun ? success(\"Dry run complete\") : success(\"Conversion complete\"),\n });\n process.stdout.write(output);\n\n if (options.verbose && !result.dryRun && result.files.length > 0) {\n console.log(\" Files written:\");\n for (const file of result.files) {\n console.log(` ${relative(process.cwd(), file) || file}`);\n }\n console.log(\"\");\n }\n\n return result;\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nexport const convertUscCommand = new Command(\"convert-usc\")\n .description(\"Convert U.S. Code XML file(s) to Markdown\")\n .argument(\"[input]\", \"Path to a USC XML file\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"./output\")\n .option(\"--titles <spec>\", \"Title(s) to convert: 1, 1-5, or 1-5,8,11\")\n .option(\"--all\", \"Convert all downloaded titles found in --input-dir\", false)\n .option(\"-i, --input-dir <dir>\", \"Directory containing USC XML files\", \"./downloads/usc/xml\")\n .addOption(\n new Option(\"-g, --granularity <level>\", \"Output granularity: section, chapter, or title\")\n .choices([\"section\", \"chapter\", \"title\"])\n .default(\"section\"),\n )\n .addOption(\n new Option(\"--link-style <style>\", \"Link style: relative, canonical, or plaintext\")\n .choices([\"relative\", \"canonical\", \"plaintext\"])\n .default(\"plaintext\"),\n )\n .option(\"--include-source-credits\", \"Include source credit annotations\", true)\n .option(\"--no-include-source-credits\", \"Exclude source credit annotations\")\n .option(\"--include-notes\", \"Include all notes (default)\", true)\n .option(\"--no-include-notes\", \"Exclude all notes\")\n .option(\"--include-editorial-notes\", \"Include editorial notes only\", false)\n .option(\"--include-statutory-notes\", \"Include statutory notes only\", false)\n .option(\"--include-amendments\", \"Include amendment history notes only\", false)\n .option(\"--dry-run\", \"Parse and report structure without writing files\", false)\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .addHelpText(\n \"after\",\n `\nInput modes (use exactly one):\n <input> Convert a single XML file\n --titles Convert specific titles by number\n --all Convert all titles in --input-dir\n\nGranularity:\n section One .md file per section (default)\n chapter One .md file per chapter, sections inlined\n title One .md file per title, entire hierarchy inlined\n\nExamples:\n $ lexbuild convert-usc --titles 1 Convert Title 1\n $ lexbuild convert-usc --titles 1-5,8,11 Convert a mix of titles\n $ lexbuild convert-usc --all -g chapter All titles, chapter-level\n $ lexbuild convert-usc --titles 26 -g title Title 26 as a single file\n $ lexbuild convert-usc --all --dry-run Preview stats only\n $ lexbuild convert-usc ./downloads/usc/xml/usc01.xml -o ./out`,\n )\n .action(async (input: string | undefined, options: ConvertCommandOptions) => {\n // Validate: must specify exactly one of <input>, --titles, or --all\n const modeCount = [input, options.titles, options.all].filter(Boolean).length;\n if (modeCount === 0) {\n console.error(\n error(\"Specify an input file, --titles <spec>, or --all (e.g. --titles 1-5,8,11)\"),\n );\n process.exit(1);\n }\n if (modeCount > 1) {\n console.error(error(\"Cannot combine <input>, --titles, and --all — use only one\"));\n process.exit(1);\n }\n\n const outputPath = resolve(options.output);\n const dryRunLabel = options.dryRun ? \" [dry-run]\" : \"\";\n\n // Single-file mode\n if (input) {\n const rawPath = resolve(input);\n const inputPath = resolveUscXmlPath(rawPath);\n if (!inputPath) {\n console.error(error(`Input file not found: ${rawPath}`));\n process.exit(1);\n }\n await convertSingleFile(inputPath, outputPath, options, `Converting${dryRunLabel}...`);\n return;\n }\n\n // Multi-title mode\n let titles: number[];\n if (options.all) {\n const inputDir = resolve(options.inputDir);\n titles = discoverTitles(inputDir);\n if (titles.length === 0) {\n console.error(error(`No USC XML files found in ${inputDir}`));\n process.exit(1);\n }\n } else {\n const titlesSpec = options.titles as string;\n try {\n titles = parseTitles(titlesSpec);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const inputDir = resolve(options.inputDir);\n const totalTitles = titles.length;\n const results: ConversionRun[] = [];\n\n const spinner = createSpinner(`Converting${dryRunLabel}...`);\n spinner.start();\n\n for (const [i, titleNum] of titles.entries()) {\n const xmlPath = titleXmlPath(inputDir, titleNum);\n\n if (!existsSync(xmlPath)) {\n spinner.stop();\n console.error(error(`XML file not found: ${xmlPath}`));\n process.exit(1);\n }\n\n spinner.text = `Converting Title ${titleNum}${dryRunLabel} (${i + 1}/${totalTitles})...`;\n\n try {\n const run = await runConversion(xmlPath, outputPath, options);\n results.push(run);\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n spinner.stop();\n\n // Summary header\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n const headerTitle = options.dryRun\n ? \"lexbuild — Conversion Summary [dry-run]\"\n : \"lexbuild — Conversion Summary\";\n const header = summaryBlock({\n title: headerTitle,\n rows: [[\"Directory\", outputRelative]],\n });\n process.stdout.write(header);\n\n // Build data table rows — adapt columns to granularity\n const granularity = options.granularity;\n let totalSections = 0;\n let totalChapters = 0;\n let totalTokens = 0;\n let totalElapsed = 0;\n\n const tableRows = results.map(({ result, elapsed }) => {\n totalSections += result.sectionsWritten;\n totalChapters += result.chapterCount;\n totalTokens += result.totalTokenEstimate;\n totalElapsed += elapsed;\n\n if (granularity === \"title\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"chapter\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.chapterCount),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.chapterCount),\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n }\n });\n\n // Totals row\n if (granularity === \"title\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"chapter\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalChapters)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalChapters)),\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n }\n\n // Table headers and footer — adapt to granularity\n const tableHeaders =\n granularity === \"title\"\n ? [\"Title\", \"Name\", \"Tokens\", \"Duration\"]\n : granularity === \"chapter\"\n ? [\"Title\", \"Name\", \"Chapters\", \"Tokens\", \"Duration\"]\n : [\"Title\", \"Name\", \"Chapters\", \"Sections\", \"Tokens\", \"Duration\"];\n\n console.log(dataTable(tableHeaders, tableRows));\n\n // Footer — show the primary unit for the chosen granularity\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n let countLabel: string;\n if (granularity === \"title\") {\n countLabel = `${totalTitles} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalChapters === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalChapters)} ${chapterWord})`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;\n }\n console.log(`\\n ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);\n console.log(\"\");\n });\n","/**\n * Shared terminal UI utilities — spinners, tables, and formatting helpers.\n */\n\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport type { Ora } from \"ora\";\nimport Table from \"cli-table3\";\n\n/** Create an ora spinner with consistent styling. */\nexport function createSpinner(text: string): Ora {\n return ora({ text, spinner: \"dots\" });\n}\n\n/** Format milliseconds as human-readable duration (e.g. \"1.5s\" or \"1m 23s\"). */\nexport function formatDuration(ms: number): string {\n const secs = ms / 1000;\n if (secs < 60) {\n return `${secs.toFixed(secs < 10 ? 2 : 1)}s`;\n }\n const mins = Math.floor(secs / 60);\n const remainSecs = Math.round(secs % 60);\n return `${mins}m ${remainSecs}s`;\n}\n\n/** Format bytes as human-readable size (e.g. \"11.0 MB\"). */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const kb = bytes / 1024;\n if (kb < 1024) return `${kb.toFixed(1)} KB`;\n const mb = kb / 1024;\n if (mb < 1024) return `${mb.toFixed(1)} MB`;\n const gb = mb / 1024;\n return `${gb.toFixed(2)} GB`;\n}\n\n/** Format a number with locale-aware separators (e.g. 1,234,567). */\nexport function formatNumber(n: number): string {\n return n.toLocaleString();\n}\n\n/** Regex matching ANSI escape sequences (SGR codes). */\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1B\\[[0-9;]*m/g;\n\n/** Strip ANSI escape codes and return visual string length. */\nfunction visualLength(s: string): number {\n return s.replace(ANSI_RE, \"\").length;\n}\n\n/**\n * Compute column widths that fill the terminal.\n * Expands `flexCol` to absorb remaining space.\n */\nfunction fillWidths(columns: string[][], colCount: number, flexCol: number): number[] | undefined {\n const termWidth = process.stdout.columns || 80;\n // Compute natural width per column (max visual length across all rows)\n const natural = Array.from<number>({ length: colCount }).fill(0);\n for (const row of columns) {\n for (let i = 0; i < colCount; i++) {\n natural[i] = Math.max(natural[i] ?? 0, visualLength(row[i] ?? \"\"));\n }\n }\n // Overhead: 2 chars left pad + 2 chars per column separator (between cols)\n const overhead = 2 + 2 * (colCount - 1);\n const naturalTotal = overhead + natural.reduce((a, b) => a + b, 0);\n if (naturalTotal >= termWidth) return undefined; // already fills or exceeds\n const extra = termWidth - naturalTotal;\n const widths = [...natural];\n widths[flexCol] = (widths[flexCol] ?? 0) + extra;\n return widths;\n}\n\n/**\n * Shared cli-table3 border characters.\n *\n * Uses 2-char left indent (\" \"), 2-char column gap (\" \"), no right border.\n * The \"mid\" intersection chars (\"──\") must match the 2-char width of \"middle\"\n * so horizontal rules span the full table width.\n */\nconst TABLE_CHARS = {\n top: \"─\",\n \"top-mid\": \"──\",\n \"top-left\": \" \",\n \"top-right\": \"\",\n bottom: \"─\",\n \"bottom-mid\": \"──\",\n \"bottom-left\": \" \",\n \"bottom-right\": \"\",\n left: \" \",\n \"left-mid\": \" \",\n right: \"\",\n \"right-mid\": \"\",\n mid: \"─\",\n \"mid-mid\": \"──\",\n middle: \" \",\n} as const;\n\n/** Shared cli-table3 style. */\nconst TABLE_STYLE: {\n head: string[];\n border: string[];\n \"padding-left\": number;\n \"padding-right\": number;\n} = {\n head: [],\n border: [],\n \"padding-left\": 0,\n \"padding-right\": 0,\n};\n\n/** Render a styled heading line. */\nexport function heading(text: string): string {\n return chalk.bold(text);\n}\n\n/** Render a success message with green checkmark. */\nexport function success(text: string): string {\n return chalk.green(`✔ ${text}`);\n}\n\n/** Render an error message with red X. */\nexport function error(text: string): string {\n return chalk.red(`✘ ${text}`);\n}\n\n/** Options for a key-value summary block. */\ninterface SummaryBlockOptions {\n /** Title line displayed above the table */\n title: string;\n /** Key-value pairs to display */\n rows: Array<[label: string, value: string]>;\n /** Optional footer line displayed below the table */\n footer?: string | undefined;\n}\n\n/** Render a key-value summary block with horizontal rules. */\nexport function summaryBlock({ title, rows, footer }: SummaryBlockOptions): string {\n const allCells = rows.map(([l, v]) => [l, v]);\n const colWidths = fillWidths(allCells, 2, 1);\n\n const table = new Table({\n chars: TABLE_CHARS,\n style: TABLE_STYLE,\n ...(colWidths ? { colWidths } : {}),\n });\n\n for (const [label, value] of rows) {\n table.push([chalk.dim(label), value]);\n }\n\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(` ${heading(title)}`);\n lines.push(table.toString());\n if (footer !== undefined) {\n lines.push(` ${footer}`);\n }\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n/** Render a multi-column data table. */\nexport function dataTable(head: string[], rows: string[][]): string {\n const table = new Table({\n head: head.map((h) => chalk.dim(h)),\n chars: TABLE_CHARS,\n style: TABLE_STYLE,\n });\n\n for (const row of rows) {\n table.push(row);\n }\n\n return table.toString();\n}\n","/**\n * Parses a title specification string into an array of title numbers.\n *\n * Supports single numbers, comma-separated lists, ranges, and mixed:\n * - `\"29\"` → `[29]`\n * - `\"1,3,8,11\"` → `[1, 3, 8, 11]`\n * - `\"1-5\"` → `[1, 2, 3, 4, 5]`\n * - `\"1-5,8,11\"` → `[1, 2, 3, 4, 5, 8, 11]`\n */\nexport function parseTitles(input: string, maxTitle = 54): number[] {\n const trimmed = input.trim();\n if (trimmed === \"\") {\n throw new Error(\"Title specification cannot be empty\");\n }\n\n const result = new Set<number>();\n const segments = trimmed.split(\",\");\n\n for (const segment of segments) {\n const part = segment.trim();\n if (part === \"\") {\n throw new Error(`Invalid title specification: \"${input}\" (empty segment)`);\n }\n\n if (part.includes(\"-\")) {\n const [startStr, endStr, ...rest] = part.split(\"-\");\n if (rest.length > 0 || !startStr || !endStr) {\n throw new Error(`Invalid range: \"${part}\" (expected format: start-end)`);\n }\n\n const start = parseIntStrict(startStr, input);\n const end = parseIntStrict(endStr, input);\n\n if (start > end) {\n throw new Error(`Invalid range: \"${part}\" (start ${start} must be ≤ end ${end})`);\n }\n\n validateTitleNumber(start, input, maxTitle);\n validateTitleNumber(end, input, maxTitle);\n\n for (let i = start; i <= end; i++) {\n result.add(i);\n }\n } else {\n const num = parseIntStrict(part, input);\n validateTitleNumber(num, input, maxTitle);\n result.add(num);\n }\n }\n\n return [...result].sort((a, b) => a - b);\n}\n\n/** Parse an integer strictly — no NaN, no floats. */\nfunction parseIntStrict(str: string, fullInput: string): number {\n const trimmed = str.trim();\n if (!/^\\d+$/.test(trimmed)) {\n throw new Error(`Invalid number \"${trimmed}\" in title specification: \"${fullInput}\"`);\n }\n return parseInt(trimmed, 10);\n}\n\n/** Validate that a title number is in the valid range. */\nfunction validateTitleNumber(num: number, fullInput: string, max = 54): void {\n if (num < 1 || num > max) {\n throw new Error(`Title number ${num} out of range (must be 1-${max}) in: \"${fullInput}\"`);\n }\n}\n","/**\n * `lexbuild download-usc` command — downloads USC XML from OLRC.\n *\n * Auto-detects the latest OLRC release point unless overridden with --release-point.\n */\n\nimport { Command } from \"commander\";\nimport { relative, resolve } from \"node:path\";\nimport { downloadTitles, detectLatestReleasePoint, FALLBACK_RELEASE_POINT } from \"@lexbuild/usc\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the download command */\ninterface DownloadCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n releasePoint?: string | undefined;\n}\n\nexport const downloadUscCommand = new Command(\"download-usc\")\n .description(\"Download U.S. Code XML from OLRC\")\n .option(\"-o, --output <dir>\", \"Download directory\", \"./downloads/usc/xml\")\n .option(\"--titles <spec>\", \"Title(s) to download: 1, 1-5, or 1-5,8,11\")\n .option(\"--all\", \"Download all 54 titles (single bulk zip)\", false)\n .option(\"--release-point <id>\", \"OLRC release point (auto-detected if omitted)\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ lexbuild download-usc --all Download all 54 titles (latest release)\n $ lexbuild download-usc --titles 1 Download Title 1 only\n $ lexbuild download-usc --titles 1-5,8,11 Download specific titles\n $ lexbuild download-usc --all -o ./my-xml Custom output directory\n $ lexbuild download-usc --all --release-point 119-73not60 Pin a specific release\n\nThe latest release point is auto-detected from the OLRC download page.\nSource: https://uscode.house.gov/download/download.shtml`,\n )\n .action(async (options: DownloadCommandOptions) => {\n // Validate: must specify --titles or --all\n if (!options.titles && !options.all) {\n console.error(error(\"Specify --titles <spec> or --all (e.g. --titles 1-5,8,11)\"));\n process.exit(1);\n }\n\n // Parse title numbers\n let titles: number[] | undefined;\n if (options.titles) {\n try {\n titles = parseTitles(options.titles);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const outputDir = resolve(options.output);\n const titleCount = titles ? titles.length : 54;\n\n // Resolve release point: use explicit flag, auto-detect, or fall back\n let releasePoint = options.releasePoint;\n if (!releasePoint) {\n const detectSpinner = createSpinner(\"Detecting latest OLRC release point...\");\n detectSpinner.start();\n const detected = await detectLatestReleasePoint();\n if (detected) {\n releasePoint = detected.releasePoint;\n detectSpinner.succeed(`Release point: ${detected.releasePoint}`);\n } else {\n releasePoint = FALLBACK_RELEASE_POINT;\n detectSpinner.warn(`Could not detect release point, using fallback: ${releasePoint}`);\n }\n }\n\n const label =\n titleCount === 1 ? `Downloading Title ${titles?.[0]}` : `Downloading ${titleCount} titles`;\n\n const spinner = createSpinner(`${label}...`);\n spinner.start();\n\n const startTime = performance.now();\n\n try {\n const result = await downloadTitles({\n outputDir,\n titles,\n releasePoint,\n });\n\n const elapsed = performance.now() - startTime;\n\n spinner.stop();\n\n // Build file table rows\n const fileRows = result.files.map((file) => [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.filePath) || file.filePath,\n ]);\n\n const totalBytes = result.files.reduce((sum, f) => sum + f.size, 0);\n\n // Summary header — show the actual release point used (may be auto-detected)\n const rpLabel = options.releasePoint\n ? result.releasePoint\n : `${result.releasePoint} (auto-detected)`;\n\n const output = summaryBlock({\n title: \"lexbuild — Download Summary\",\n rows: [\n [\"Release Point\", rpLabel],\n [\"Directory\", relative(process.cwd(), outputDir) || outputDir],\n ],\n });\n process.stdout.write(output);\n\n // File table\n if (fileRows.length > 0) {\n console.log(dataTable([\"Title\", \"Size\", \"File\"], fileRows));\n }\n\n // Errors\n if (result.errors.length > 0) {\n console.log(\"\");\n for (const err of result.errors) {\n console.log(` ${error(`Title ${err.titleNumber}: ${err.message}`)}`);\n }\n }\n\n // Footer\n const titleWord = result.files.length === 1 ? \"title\" : \"titles\";\n const summary = `Downloaded ${result.files.length} ${titleWord} (${formatBytes(totalBytes)}) in ${formatDuration(elapsed)}`;\n const failSuffix = result.errors.length > 0 ? ` (${result.errors.length} failed)` : \"\";\n console.log(` ${success(summary + failSuffix)}`);\n console.log(\"\");\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n","/**\n * `lexbuild convert-ecfr` command — converts eCFR XML files to Markdown.\n */\n\nimport chalk from \"chalk\";\nimport { Command, Option } from \"commander\";\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { join, relative, resolve } from \"node:path\";\nimport { convertEcfrTitle } from \"@lexbuild/ecfr\";\nimport type { EcfrConvertOptions, EcfrConvertResult } from \"@lexbuild/ecfr\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n formatNumber,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Parsed options from the convert-ecfr command */\ninterface ConvertEcfrCommandOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n inputDir: string;\n granularity: \"section\" | \"part\" | \"chapter\" | \"title\";\n linkStyle: \"relative\" | \"canonical\" | \"plaintext\";\n includeSourceCredits: boolean;\n includeNotes: boolean;\n includeEditorialNotes: boolean;\n includeStatutoryNotes: boolean;\n includeAmendments: boolean;\n dryRun: boolean;\n verbose: boolean;\n}\n\n/** Build EcfrConvertOptions from CLI flags. */\nfunction buildConvertOptions(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n): EcfrConvertOptions {\n const hasSelectiveFlags =\n options.includeEditorialNotes || options.includeStatutoryNotes || options.includeAmendments;\n const includeNotes = hasSelectiveFlags ? false : options.includeNotes;\n\n return {\n input: inputPath,\n output: outputPath,\n granularity: options.granularity,\n linkStyle: options.linkStyle,\n includeSourceCredits: options.includeSourceCredits,\n includeNotes,\n includeEditorialNotes: options.includeEditorialNotes,\n includeStatutoryNotes: options.includeStatutoryNotes,\n includeAmendments: options.includeAmendments,\n dryRun: options.dryRun,\n };\n}\n\n/** Regex matching eCFR XML filenames like ECFR-title1.xml, ECFR-title17.xml */\nconst ECFR_XML_RE = /^ECFR-title(\\d+)\\.xml$/;\n\n/** Resolve the XML file path for a given eCFR title number. */\nfunction titleXmlPath(inputDir: string, titleNum: number): string {\n return join(inputDir, `ECFR-title${titleNum}.xml`);\n}\n\n/** Scan a directory for eCFR XML files and return their title numbers, sorted. */\nfunction discoverEcfrTitles(inputDir: string): number[] {\n if (!existsSync(inputDir)) return [];\n\n return readdirSync(inputDir)\n .map((name) => ECFR_XML_RE.exec(name))\n .filter((m): m is RegExpExecArray => m !== null)\n .map((m) => parseInt(m[1] ?? \"0\", 10))\n .sort((a, b) => a - b);\n}\n\n/** Result from running a conversion including elapsed time. */\ninterface ConversionRun {\n result: EcfrConvertResult;\n elapsed: number;\n}\n\n/** Run a conversion without printing output. */\nasync function runConversion(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n): Promise<ConversionRun> {\n const startTime = performance.now();\n const result = await convertEcfrTitle(buildConvertOptions(inputPath, outputPath, options));\n const elapsed = performance.now() - startTime;\n return { result, elapsed };\n}\n\n/** Convert a single file and print its detailed summary. */\nasync function convertSingleFile(\n inputPath: string,\n outputPath: string,\n options: ConvertEcfrCommandOptions,\n spinnerLabel: string,\n) {\n const spinner = createSpinner(spinnerLabel);\n spinner.start();\n\n try {\n const { result, elapsed } = await runConversion(inputPath, outputPath, options);\n spinner.stop();\n\n const rows: Array<[string, string]> = [];\n if (options.granularity === \"section\") {\n rows.push([\"Sections\", formatNumber(result.sectionsWritten)]);\n rows.push([\"Parts\", formatNumber(result.partCount)]);\n } else if (options.granularity === \"part\") {\n rows.push([\"Parts\", formatNumber(result.partCount)]);\n } else if (options.granularity === \"chapter\") {\n rows.push([\"Chapters\", formatNumber(result.sectionsWritten)]);\n }\n rows.push([\"Est. Tokens\", formatNumber(result.totalTokenEstimate)]);\n\n if (!result.dryRun) {\n rows.push([\"Files Written\", formatNumber(result.files.length)]);\n }\n\n rows.push(\n [\"Peak Memory\", formatBytes(result.peakMemoryBytes)],\n [\"Duration\", formatDuration(elapsed)],\n );\n\n const titleLabel = result.dryRun\n ? `lexbuild — eCFR Title ${result.titleNumber}: ${result.titleName} [dry-run]`\n : `lexbuild — eCFR Title ${result.titleNumber}: ${result.titleName}`;\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n\n const output = summaryBlock({\n title: titleLabel,\n rows: [...rows, [\"Output\", outputRelative]],\n footer: result.dryRun ? success(\"Dry run complete\") : success(\"Conversion complete\"),\n });\n process.stdout.write(output);\n\n if (options.verbose && !result.dryRun && result.files.length > 0) {\n console.log(\" Files written:\");\n for (const file of result.files) {\n console.log(` ${relative(process.cwd(), file) || file}`);\n }\n console.log(\"\");\n }\n\n return result;\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nexport const convertEcfrCommand = new Command(\"convert-ecfr\")\n .description(\"Convert eCFR XML file(s) to Markdown\")\n .argument(\"[input]\", \"Path to an eCFR XML file\")\n .option(\"-o, --output <dir>\", \"Output directory\", \"./output\")\n .option(\"--titles <spec>\", \"Title(s) to convert: 1, 1-5, or 1-5,17\")\n .option(\"--all\", \"Convert all downloaded eCFR titles found in --input-dir\", false)\n .option(\"-i, --input-dir <dir>\", \"Directory containing eCFR XML files\", \"./downloads/ecfr/xml\")\n .addOption(\n new Option(\"-g, --granularity <level>\", \"Output granularity: section, part, chapter, or title\")\n .choices([\"section\", \"part\", \"chapter\", \"title\"])\n .default(\"section\"),\n )\n .addOption(\n new Option(\"--link-style <style>\", \"Link style: relative, canonical, or plaintext\")\n .choices([\"relative\", \"canonical\", \"plaintext\"])\n .default(\"plaintext\"),\n )\n .option(\"--include-source-credits\", \"Include source credit annotations\", true)\n .option(\"--no-include-source-credits\", \"Exclude source credit annotations\")\n .option(\"--include-notes\", \"Include all notes (default)\", true)\n .option(\"--no-include-notes\", \"Exclude all notes\")\n .option(\"--include-editorial-notes\", \"Include editorial notes only\", false)\n .option(\"--include-statutory-notes\", \"Include statutory/regulatory notes only\", false)\n .option(\"--include-amendments\", \"Include amendment history notes only\", false)\n .option(\"--dry-run\", \"Parse and report structure without writing files\", false)\n .option(\"-v, --verbose\", \"Enable verbose logging\", false)\n .addHelpText(\n \"after\",\n `\nInput modes (use exactly one):\n <input> Convert a single eCFR XML file\n --titles Convert specific titles by number\n --all Convert all titles in --input-dir\n\nGranularity:\n section One .md file per section (default)\n part One .md file per part, sections inlined\n chapter One .md file per chapter, parts and sections inlined\n title One .md file per title, entire hierarchy inlined\n\nExamples:\n $ lexbuild convert-ecfr --titles 1 Convert eCFR Title 1\n $ lexbuild convert-ecfr --titles 1-5,17 Convert specific titles\n $ lexbuild convert-ecfr --all -g part All titles, part-level\n $ lexbuild convert-ecfr --all --dry-run Preview stats only\n $ lexbuild convert-ecfr ./downloads/ecfr/xml/ECFR-title1.xml -o ./out`,\n )\n .action(async (input: string | undefined, options: ConvertEcfrCommandOptions) => {\n const modeCount = [input, options.titles, options.all].filter(Boolean).length;\n if (modeCount === 0) {\n console.error(\n error(\"Specify an input file, --titles <spec>, or --all (e.g. --titles 1-5,17)\"),\n );\n process.exit(1);\n }\n if (modeCount > 1) {\n console.error(error(\"Cannot combine <input>, --titles, and --all — use only one\"));\n process.exit(1);\n }\n\n const outputPath = resolve(options.output);\n const dryRunLabel = options.dryRun ? \" [dry-run]\" : \"\";\n\n // Single-file mode\n if (input) {\n const inputPath = resolve(input);\n if (!existsSync(inputPath)) {\n console.error(error(`Input file not found: ${inputPath}`));\n process.exit(1);\n }\n await convertSingleFile(inputPath, outputPath, options, `Converting eCFR${dryRunLabel}...`);\n return;\n }\n\n // Multi-title mode\n let titles: number[];\n if (options.all) {\n const inputDir = resolve(options.inputDir);\n titles = discoverEcfrTitles(inputDir);\n if (titles.length === 0) {\n console.error(error(`No eCFR XML files found in ${inputDir}`));\n process.exit(1);\n }\n } else {\n const titlesSpec = options.titles as string;\n try {\n titles = parseTitles(titlesSpec, 50);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const inputDir = resolve(options.inputDir);\n const totalTitles = titles.length;\n const results: ConversionRun[] = [];\n\n const spinner = createSpinner(`Converting eCFR${dryRunLabel}...`);\n spinner.start();\n\n for (const [i, titleNum] of titles.entries()) {\n const xmlPath = titleXmlPath(inputDir, titleNum);\n\n if (!existsSync(xmlPath)) {\n spinner.stop();\n console.error(error(`XML file not found: ${xmlPath}`));\n process.exit(1);\n }\n\n spinner.text = `Converting eCFR Title ${titleNum}${dryRunLabel} (${i + 1}/${totalTitles})...`;\n\n try {\n const run = await runConversion(xmlPath, outputPath, options);\n results.push(run);\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n }\n\n spinner.stop();\n\n const outputRelative = relative(process.cwd(), outputPath) || outputPath;\n const headerTitle = options.dryRun\n ? \"lexbuild — eCFR Conversion Summary [dry-run]\"\n : \"lexbuild — eCFR Conversion Summary\";\n const header = summaryBlock({\n title: headerTitle,\n rows: [[\"Directory\", outputRelative]],\n });\n process.stdout.write(header);\n\n const granularity = options.granularity;\n let totalSections = 0;\n let totalParts = 0;\n let totalTokens = 0;\n let totalElapsed = 0;\n\n const tableRows = results.map(({ result, elapsed }) => {\n totalSections += result.sectionsWritten;\n totalParts += result.partCount;\n totalTokens += result.totalTokenEstimate;\n totalElapsed += elapsed;\n\n if (granularity === \"title\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"chapter\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else if (granularity === \"part\") {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.partCount),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n } else {\n return [\n result.titleNumber,\n result.titleName,\n formatNumber(result.partCount),\n formatNumber(result.sectionsWritten),\n formatNumber(result.totalTokenEstimate),\n formatDuration(elapsed),\n ];\n }\n });\n\n if (granularity === \"title\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"chapter\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else if (granularity === \"part\") {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalParts)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n } else {\n tableRows.push([\n chalk.bold(\"Total\"),\n \"\",\n chalk.bold(formatNumber(totalParts)),\n chalk.bold(formatNumber(totalSections)),\n chalk.bold(formatNumber(totalTokens)),\n chalk.bold(formatDuration(totalElapsed)),\n ]);\n }\n\n const tableHeaders =\n granularity === \"title\"\n ? [\"Title\", \"Name\", \"Tokens\", \"Duration\"]\n : granularity === \"chapter\"\n ? [\"Title\", \"Name\", \"Chapters\", \"Tokens\", \"Duration\"]\n : granularity === \"part\"\n ? [\"Title\", \"Name\", \"Parts\", \"Tokens\", \"Duration\"]\n : [\"Title\", \"Name\", \"Parts\", \"Sections\", \"Tokens\", \"Duration\"];\n\n console.log(dataTable(tableHeaders, tableRows));\n\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n let countLabel: string;\n if (granularity === \"title\") {\n countLabel = `${totalTitles} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalSections === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${chapterWord})`;\n } else if (granularity === \"part\") {\n const partWord = totalParts === 1 ? \"part\" : \"parts\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalParts)} ${partWord})`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;\n }\n console.log(`\\n ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);\n console.log(\"\");\n });\n","/**\n * `lexbuild download-ecfr` command — downloads eCFR XML from govinfo or eCFR API.\n */\n\nimport { Command } from \"commander\";\nimport { relative, resolve } from \"node:path\";\nimport { downloadEcfrTitles, downloadEcfrTitlesFromApi } from \"@lexbuild/ecfr\";\nimport {\n createSpinner,\n summaryBlock,\n dataTable,\n formatDuration,\n formatBytes,\n success,\n error,\n} from \"../ui.js\";\nimport { parseTitles } from \"../parse-titles.js\";\n\n/** Valid download source values */\ntype EcfrSource = \"govinfo\" | \"ecfr-api\";\n\n/** Parsed options from the download-ecfr command */\ninterface DownloadEcfrOptions {\n output: string;\n titles?: string | undefined;\n all: boolean;\n source: EcfrSource;\n date?: string | undefined;\n}\n\nexport const downloadEcfrCommand = new Command(\"download-ecfr\")\n .description(\"Download eCFR XML from govinfo or eCFR API\")\n .option(\"-o, --output <dir>\", \"Download directory\", \"./downloads/ecfr/xml\")\n .option(\"--titles <spec>\", \"Title(s) to download: 1, 1-5, or 1-5,8,17\")\n .option(\"--all\", \"Download all 50 eCFR titles\", false)\n .option(\n \"--source <source>\",\n \"Download source: ecfr-api (daily-updated) or govinfo (bulk data)\",\n \"ecfr-api\",\n )\n .option(\"--date <YYYY-MM-DD>\", \"Point-in-time date (ecfr-api source only)\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ lexbuild download-ecfr --all Download all titles from eCFR API\n $ lexbuild download-ecfr --titles 1 Download Title 1 only\n $ lexbuild download-ecfr --titles 1-5,17 Download specific titles\n $ lexbuild download-ecfr --all --date 2026-01-01 Point-in-time download\n $ lexbuild download-ecfr --all --source govinfo Download from govinfo bulk data\n\nSources:\n ecfr-api — eCFR API from ecfr.gov (default, daily-updated, point-in-time support)\n govinfo — Bulk XML from govinfo.gov (updates irregularly)`,\n )\n .action(async (options: DownloadEcfrOptions) => {\n if (!options.titles && !options.all) {\n console.error(error(\"Specify --titles <spec> or --all (e.g. --titles 1-5,17)\"));\n process.exit(1);\n }\n\n // Validate source\n const validSources: EcfrSource[] = [\"govinfo\", \"ecfr-api\"];\n if (!validSources.includes(options.source)) {\n console.error(\n error(`Invalid source \"${options.source}\". Valid sources: ${validSources.join(\", \")}`),\n );\n process.exit(1);\n }\n\n // --date is only valid with ecfr-api source\n if (options.date && options.source !== \"ecfr-api\") {\n console.error(error(\"--date is only supported with --source ecfr-api\"));\n process.exit(1);\n }\n\n // Validate date format if provided\n if (options.date && !/^\\d{4}-\\d{2}-\\d{2}$/.test(options.date)) {\n console.error(error(\"--date must be in YYYY-MM-DD format\"));\n process.exit(1);\n }\n\n let titles: number[] | undefined;\n if (options.titles) {\n try {\n titles = parseTitles(options.titles, 50);\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n const outputDir = resolve(options.output);\n const titleCount = titles ? titles.length : 50;\n const sourceLabel = options.source === \"ecfr-api\" ? \"eCFR API\" : \"govinfo\";\n const label =\n titleCount === 1\n ? `Downloading eCFR Title ${titles?.[0]} from ${sourceLabel}`\n : `Downloading ${titleCount} eCFR titles from ${sourceLabel}`;\n\n const spinner = createSpinner(`${label}...`);\n spinner.start();\n\n const startTime = performance.now();\n\n try {\n if (options.source === \"ecfr-api\") {\n await downloadFromApi(options, titles, outputDir, spinner, startTime);\n } else {\n await downloadFromGovinfo(titles, outputDir, spinner, startTime);\n }\n } catch (err) {\n spinner.fail(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\n/** Download from govinfo bulk data (existing behavior) */\nasync function downloadFromGovinfo(\n titles: number[] | undefined,\n outputDir: string,\n spinner: ReturnType<typeof createSpinner>,\n startTime: number,\n): Promise<void> {\n const result = await downloadEcfrTitles({\n output: outputDir,\n titles,\n });\n\n const elapsed = performance.now() - startTime;\n spinner.stop();\n\n const fileRows = result.files.map((file) => [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.path) || file.path,\n ]);\n\n const output = summaryBlock({\n title: \"lexbuild — eCFR Download Summary\",\n rows: [\n [\"Source\", \"govinfo.gov/bulkdata/ECFR\"],\n [\"Directory\", relative(process.cwd(), outputDir) || outputDir],\n ],\n });\n process.stdout.write(output);\n\n if (fileRows.length > 0) {\n console.log(dataTable([\"Title\", \"Size\", \"File\"], fileRows));\n }\n\n const titleWord = result.titlesDownloaded === 1 ? \"title\" : \"titles\";\n const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;\n console.log(` ${success(summary)}`);\n console.log(\"\");\n}\n\n/** Download from eCFR API (daily-updated, point-in-time) */\nasync function downloadFromApi(\n options: DownloadEcfrOptions,\n titles: number[] | undefined,\n outputDir: string,\n spinner: ReturnType<typeof createSpinner>,\n startTime: number,\n): Promise<void> {\n const result = await downloadEcfrTitlesFromApi({\n output: outputDir,\n titles,\n date: options.date,\n });\n\n const elapsed = performance.now() - startTime;\n spinner.stop();\n\n const fileRows = result.files.map((file) => [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.path) || file.path,\n ]);\n\n const summaryRows: [string, string][] = [\n [\"Source\", \"ecfr.gov/api/versioner/v1\"],\n [\"As of date\", result.asOfDate],\n [\"Directory\", relative(process.cwd(), outputDir) || outputDir],\n ];\n\n const output = summaryBlock({\n title: \"lexbuild — eCFR Download Summary\",\n rows: summaryRows,\n });\n process.stdout.write(output);\n\n if (fileRows.length > 0) {\n console.log(dataTable([\"Title\", \"Size\", \"File\"], fileRows));\n }\n\n const titleWord = result.titlesDownloaded === 1 ? \"title\" : \"titles\";\n const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;\n console.log(` ${success(summary)}`);\n console.log(\"\");\n}\n"],"mappings":"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","names":["Command","chalk","inputDir","chalk","Command","relative","resolve","Command","resolve","relative","chalk","Command","Option","existsSync","readdirSync","join","relative","resolve","buildConvertOptions","titleXmlPath","join","existsSync","readdirSync","runConversion","convertSingleFile","relative","Command","Option","resolve","inputDir","chalk","Command","relative","resolve","Command","resolve","relative","require","Command"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexbuild/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Compiler for legal and civic texts. Converts disparate statutory data into structured formats optimized for AI, RAG, and semantic search.",
|
|
5
5
|
"author": "Chris Thomas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"ora": "^8.2.0",
|
|
55
55
|
"pino": "^9.6.0",
|
|
56
56
|
"pino-pretty": "^13.0.0",
|
|
57
|
-
"@lexbuild/core": "1.
|
|
58
|
-
"@lexbuild/
|
|
59
|
-
"@lexbuild/
|
|
57
|
+
"@lexbuild/core": "1.10.0",
|
|
58
|
+
"@lexbuild/ecfr": "1.10.0",
|
|
59
|
+
"@lexbuild/usc": "1.10.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^25.3.2",
|