@lexbuild/cli 1.9.4 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,151 +2,129 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/%40lexbuild%2Fcli?style=for-the-badge)](https://www.npmjs.com/package/@lexbuild/cli)
4
4
  [![CI](https://img.shields.io/github/actions/workflow/status/chris-c-thomas/LexBuild/ci.yml?style=for-the-badge&label=CI)](https://github.com/chris-c-thomas/LexBuild/actions/workflows/ci.yml)
5
- [![license](https://img.shields.io/github/license/chris-c-thomas/LexBuild?style=for-the-badge)](https://github.com/chris-c-thomas/LexBuild)
5
+ [![license](https://img.shields.io/github/license/chris-c-thomas/LexBuild?style=for-the-badge)](https://github.com/chris-c-thomas/LexBuild/blob/main/LICENSE)
6
6
 
7
- This package is part of the [LexBuild monorepo](https://github.com/chris-c-thomas/LexBuild), a tool that converts U.S. legal XML into structured Markdown optimized for AI, RAG pipelines, and semantic search. See the monorepo for full documentation, architecture details, and contribution guidelines.
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
- ```bash
22
- npx @lexbuild/cli download-usc --all
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
- # Download and convert all 54 titles
32
- lexbuild download-usc --all && lexbuild convert-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
- # Start small — download and convert Title 1
35
- lexbuild download-usc --titles 1 && lexbuild convert-usc --titles 1
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
- ```bash
44
- # Download and convert all 50 titles
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
- ### U.S. Code
54
-
55
- #### `lexbuild download-usc`
37
+ ### `download-usc`
56
38
 
57
- Fetch U.S. Code XML files from the OLRC.
39
+ Download U.S. Code XML from the OLRC. Auto-detects the latest release point.
58
40
 
59
41
  ```bash
60
- lexbuild download-usc --titles 1 # Single title
61
- lexbuild download-usc --titles 1-5,8,11 # Range + specific titles
62
- lexbuild download-usc --all # All 54 titles (single bulk zip)
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) to download: `1`, `1-5`, `1-5,8,11` |
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>` | current | OLRC release point |
52
+ | `--release-point <id>` | auto-detected | Pin a specific OLRC release point |
71
53
 
72
- #### `lexbuild convert-usc`
54
+ ### `convert-usc`
73
55
 
74
56
  Convert downloaded USC XML to Markdown.
75
57
 
76
58
  ```bash
77
- lexbuild convert-usc --titles 1 # By title number
78
- lexbuild convert-usc --all # All downloaded titles
79
- lexbuild convert-usc ./downloads/usc/xml/usc01.xml # Direct file path
80
- lexbuild convert-usc --titles 1 -g chapter # Chapter-level output
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 directory for XML files |
69
+ | `-i, --input-dir <dir>` | `./downloads/usc/xml` | Input XML directory |
91
70
  | `-o, --output <dir>` | `./output` | Output directory |
92
- | `-g, --granularity <level>` | `section` | `section`, `chapter`, or `title` |
93
- | `--link-style <style>` | `plaintext` | `plaintext`, `canonical`, or `relative` |
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 files |
100
- | `-v, --verbose` | — | Verbose logging |
78
+ | `--dry-run` | — | Parse and report without writing |
79
+ | `-v, --verbose` | — | Verbose file output |
101
80
 
102
- ### eCFR (Code of Federal Regulations)
81
+ ### `download-ecfr`
103
82
 
104
- #### `lexbuild download-ecfr`
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 17 # Single title
110
- lexbuild download-ecfr --titles 1-5,17 # Range + specific titles
111
- lexbuild download-ecfr --all # All 50 titles
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) to download: `1`, `1-5`, `1-5,17` |
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
- #### `lexbuild convert-ecfr`
100
+ ### `convert-ecfr`
121
101
 
122
102
  Convert downloaded eCFR XML to Markdown.
123
103
 
124
104
  ```bash
125
- lexbuild convert-ecfr --titles 17 # By title number
126
- lexbuild convert-ecfr --all # All downloaded titles
127
- lexbuild convert-ecfr ./downloads/ecfr/xml/ECFR-title17.xml # Direct file path
128
- lexbuild convert-ecfr --titles 17 -g part # Part-level output
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 directory for XML files |
115
+ | `-i, --input-dir <dir>` | `./downloads/ecfr/xml` | Input XML directory |
138
116
  | `-o, --output <dir>` | `./output` | Output directory |
139
- | `-g, --granularity <level>` | `section` | `section`, `part`, `chapter`, or `title` |
140
- | `--link-style <style>` | `plaintext` | `plaintext`, `canonical`, or `relative` |
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/regulatory notes only |
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 files |
147
- | `-v, --verbose` | — | Verbose logging |
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
- Each file includes YAML frontmatter with source-specific metadata (`source`, `legal_status`, identifier, hierarchy context) followed by the legal text. Section and chapter/part granularities also generate `_meta.json` sidecar files and `README.md` summaries per title.
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-30 seconds on modern machines. SAX streaming keeps memory bounded for even the largest titles (100MB+ XML).
150
+ The full U.S. Code — all 54 titles, 60,000+ sections, ~85 million estimated tokens — converts in about 2030 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
@@ -416,16 +416,16 @@ Examples:
416
416
  }
417
417
  const tableHeaders = granularity === "title" ? ["Title", "Name", "Tokens", "Duration"] : granularity === "chapter" ? ["Title", "Name", "Chapters", "Tokens", "Duration"] : ["Title", "Name", "Chapters", "Sections", "Tokens", "Duration"];
418
418
  console.log(dataTable(tableHeaders, tableRows));
419
- const titleWord = totalTitles === 1 ? "title" : "titles";
420
419
  let countLabel;
421
420
  if (granularity === "title") {
422
- countLabel = `${totalTitles} ${titleWord}`;
421
+ const titleWord = totalTitles === 1 ? "title" : "titles";
422
+ countLabel = `${formatNumber(totalTitles)} ${titleWord}`;
423
423
  } else if (granularity === "chapter") {
424
424
  const chapterWord = totalChapters === 1 ? "chapter" : "chapters";
425
- countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalChapters)} ${chapterWord})`;
425
+ countLabel = `${formatNumber(totalChapters)} ${chapterWord}`;
426
426
  } else {
427
427
  const sectionWord = totalSections === 1 ? "section" : "sections";
428
- countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;
428
+ countLabel = `${formatNumber(totalSections)} ${sectionWord}`;
429
429
  }
430
430
  console.log(`
431
431
  ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);
@@ -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, CURRENT_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 identifier", CURRENT_RELEASE_POINT).addHelpText(
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: options.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", options.releasePoint],
502
+ ["Release Point", rpLabel],
487
503
  ["Directory", relative2(process.cwd(), outputDir) || outputDir]
488
504
  ]
489
505
  });
@@ -760,19 +776,19 @@ Examples:
760
776
  }
761
777
  const tableHeaders = granularity === "title" ? ["Title", "Name", "Tokens", "Duration"] : granularity === "chapter" ? ["Title", "Name", "Chapters", "Tokens", "Duration"] : granularity === "part" ? ["Title", "Name", "Parts", "Tokens", "Duration"] : ["Title", "Name", "Parts", "Sections", "Tokens", "Duration"];
762
778
  console.log(dataTable(tableHeaders, tableRows));
763
- const titleWord = totalTitles === 1 ? "title" : "titles";
764
779
  let countLabel;
765
780
  if (granularity === "title") {
766
- countLabel = `${totalTitles} ${titleWord}`;
781
+ const titleWord = totalTitles === 1 ? "title" : "titles";
782
+ countLabel = `${formatNumber(totalTitles)} ${titleWord}`;
767
783
  } else if (granularity === "chapter") {
768
784
  const chapterWord = totalSections === 1 ? "chapter" : "chapters";
769
- countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${chapterWord})`;
785
+ countLabel = `${formatNumber(totalSections)} ${chapterWord}`;
770
786
  } else if (granularity === "part") {
771
787
  const partWord = totalParts === 1 ? "part" : "parts";
772
- countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalParts)} ${partWord})`;
788
+ countLabel = `${formatNumber(totalParts)} ${partWord}`;
773
789
  } else {
774
790
  const sectionWord = totalSections === 1 ? "section" : "sections";
775
- countLabel = `${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord})`;
791
+ countLabel = `${formatNumber(totalSections)} ${sectionWord}`;
776
792
  }
777
793
  console.log(`
778
794
  ${success(`Converted ${countLabel} in ${formatDuration(totalElapsed)}`)}`);
@@ -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 bulk data repository").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).addHelpText(
801
+ import { downloadEcfrTitles, downloadEcfrTitlesFromApi, fetchEcfrTitlesMeta } 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 Download all 50 titles
791
- $ lexbuild download-ecfr --titles 1 Download Title 1 only
792
- $ lexbuild download-ecfr --titles 1-5,17 Download specific titles
793
- $ lexbuild download-ecfr --all -o ./my-xml Custom output directory
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
- Source: https://www.govinfo.gov/bulkdata/ECFR`
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,139 @@ 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 label = titleCount === 1 ? `Downloading eCFR Title ${titles?.[0]}` : `Downloading ${titleCount} eCFR titles`;
850
+ const sourceLabel = options.source === "ecfr-api" ? "eCFR API" : "govinfo";
851
+ let meta;
852
+ if (options.source === "ecfr-api" && !options.date) {
853
+ try {
854
+ meta = await fetchEcfrTitlesMeta();
855
+ } catch (err) {
856
+ console.error(error(err instanceof Error ? err.message : String(err)));
857
+ process.exit(1);
858
+ }
859
+ }
860
+ let dateLabel = "";
861
+ let statusNote = "";
862
+ if (options.date) {
863
+ dateLabel = ` as of ${options.date}`;
864
+ } else if (meta) {
865
+ const primaryDate = meta.importInProgress ? (() => {
866
+ const prev = new Date(meta.date);
867
+ prev.setDate(prev.getDate() - 1);
868
+ return prev.toISOString().slice(0, 10);
869
+ })() : meta.date;
870
+ dateLabel = ` as of ${primaryDate}`;
871
+ const processingTitles = meta.titles.filter((t) => t.processingInProgress && !t.reserved);
872
+ if (meta.importInProgress && processingTitles.length > 0) {
873
+ const titleWord = processingTitles.length === 1 ? "title uses" : "titles use";
874
+ statusNote = ` (import in progress, ${processingTitles.length} ${titleWord} earlier dates)`;
875
+ } else if (meta.importInProgress) {
876
+ statusNote = " (import in progress, using previous day)";
877
+ } else if (processingTitles.length > 0) {
878
+ const nums = processingTitles.map((t) => t.number).join(", ");
879
+ statusNote = ` (Title ${nums} processing, using earlier date)`;
880
+ }
881
+ }
882
+ const label = titleCount === 1 ? `Downloading eCFR Title ${titles?.[0]} from ${sourceLabel}${dateLabel}${statusNote}` : `Downloading ${titleCount} eCFR titles from ${sourceLabel}${dateLabel}${statusNote}`;
813
883
  const spinner = createSpinner(`${label}...`);
814
884
  spinner.start();
815
885
  const startTime = performance.now();
816
886
  try {
817
- const result = await downloadEcfrTitles({
818
- output: outputDir,
819
- titles
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));
887
+ if (options.source === "ecfr-api") {
888
+ await downloadFromApi(options, titles, outputDir, spinner, startTime, meta);
889
+ } else {
890
+ await downloadFromGovinfo(titles, outputDir, spinner, startTime);
838
891
  }
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
892
  } catch (err) {
844
893
  spinner.fail(err instanceof Error ? err.message : String(err));
845
894
  process.exit(1);
846
895
  }
847
896
  });
897
+ async function downloadFromGovinfo(titles, outputDir, spinner, startTime) {
898
+ const result = await downloadEcfrTitles({
899
+ output: outputDir,
900
+ titles
901
+ });
902
+ const elapsed = performance.now() - startTime;
903
+ spinner.stop();
904
+ const fileRows = result.files.map((file) => [
905
+ String(file.titleNumber),
906
+ formatBytes(file.size),
907
+ relative4(outputDir, file.path) || file.path
908
+ ]);
909
+ const output = summaryBlock({
910
+ title: "lexbuild \u2014 eCFR Download Summary",
911
+ rows: [
912
+ ["Source", "govinfo.gov/bulkdata/ECFR"],
913
+ ["Directory", relative4(process.cwd(), outputDir) || outputDir]
914
+ ]
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
+ }
925
+ async function downloadFromApi(options, titles, outputDir, spinner, startTime, meta) {
926
+ const result = await downloadEcfrTitlesFromApi({
927
+ output: outputDir,
928
+ titles,
929
+ date: options.date,
930
+ titlesMeta: meta
931
+ });
932
+ const elapsed = performance.now() - startTime;
933
+ spinner.stop();
934
+ const uniqueDates = new Set(result.files.map((f) => f.asOfDate));
935
+ const dateDisplay = uniqueDates.size <= 1 ? result.asOfDate : `${result.asOfDate} (${uniqueDates.size - 1} titles at earlier dates)`;
936
+ const fileRows = result.files.map((file) => {
937
+ const row = [
938
+ String(file.titleNumber),
939
+ formatBytes(file.size),
940
+ relative4(outputDir, file.path) || file.path
941
+ ];
942
+ if (file.asOfDate !== result.asOfDate) {
943
+ row.push(file.asOfDate);
944
+ }
945
+ return row;
946
+ });
947
+ const summaryRows = [
948
+ ["Source", "ecfr.gov/api/versioner/v1"],
949
+ ["As of date", dateDisplay],
950
+ ["Directory", relative4(process.cwd(), outputDir) || outputDir]
951
+ ];
952
+ const output = summaryBlock({
953
+ title: "lexbuild \u2014 eCFR Download Summary",
954
+ rows: summaryRows
955
+ });
956
+ process.stdout.write(output);
957
+ const hasMultipleDates = uniqueDates.size > 1;
958
+ const headings = hasMultipleDates ? ["Title", "Size", "File", "Date"] : ["Title", "Size", "File"];
959
+ if (fileRows.length > 0) {
960
+ console.log(dataTable(headings, fileRows));
961
+ }
962
+ if (result.failed.length > 0) {
963
+ const processing = result.failed.filter((f) => f.status === 503);
964
+ const other = result.failed.filter((f) => f.status !== 503);
965
+ if (processing.length > 0) {
966
+ const nums = processing.map((f) => `Title ${f.titleNumber}`).join(", ");
967
+ console.log(` ${error(`Unavailable (processing on server): ${nums}`)}`);
968
+ console.log(` The eCFR API cannot serve these titles while an import is in progress.`);
969
+ console.log(
970
+ ` Re-run this command later to download them. Existing local files (if any) are preserved.`
971
+ );
972
+ }
973
+ if (other.length > 0) {
974
+ const nums = other.map((f) => `Title ${f.titleNumber} (${f.status})`).join(", ");
975
+ console.log(` ${error(`Failed: ${nums}`)}`);
976
+ }
977
+ }
978
+ const titleWord = result.titlesDownloaded === 1 ? "title" : "titles";
979
+ const summary = `Downloaded ${result.titlesDownloaded} ${titleWord} (${formatBytes(result.totalBytes)}) in ${formatDuration(elapsed)}`;
980
+ console.log(` ${success(summary)}`);
981
+ console.log("");
982
+ }
848
983
 
849
984
  // src/index.ts
850
985
  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 that was converted\n let countLabel: string;\n if (granularity === \"title\") {\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n countLabel = `${formatNumber(totalTitles)} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalChapters === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${formatNumber(totalChapters)} ${chapterWord}`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${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 // Footer — show the primary unit that was converted\n let countLabel: string;\n if (granularity === \"title\") {\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n countLabel = `${formatNumber(totalTitles)} ${titleWord}`;\n } else if (granularity === \"chapter\") {\n const chapterWord = totalSections === 1 ? \"chapter\" : \"chapters\";\n countLabel = `${formatNumber(totalSections)} ${chapterWord}`;\n } else if (granularity === \"part\") {\n const partWord = totalParts === 1 ? \"part\" : \"parts\";\n countLabel = `${formatNumber(totalParts)} ${partWord}`;\n } else {\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n countLabel = `${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, fetchEcfrTitlesMeta } from \"@lexbuild/ecfr\";\nimport type { EcfrTitlesResponse } 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\n // For ecfr-api, fetch metadata upfront to resolve dates and display status\n let meta: EcfrTitlesResponse | undefined;\n if (options.source === \"ecfr-api\" && !options.date) {\n try {\n meta = await fetchEcfrTitlesMeta();\n } catch (err) {\n console.error(error(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n }\n\n // Build spinner label with date info\n let dateLabel = \"\";\n let statusNote = \"\";\n if (options.date) {\n dateLabel = ` as of ${options.date}`;\n } else if (meta) {\n const primaryDate = meta.importInProgress\n ? (() => {\n const prev = new Date(meta.date);\n prev.setDate(prev.getDate() - 1);\n return prev.toISOString().slice(0, 10);\n })()\n : meta.date;\n dateLabel = ` as of ${primaryDate}`;\n\n // Check for titles with individual processing\n const processingTitles = meta.titles.filter((t) => t.processingInProgress && !t.reserved);\n if (meta.importInProgress && processingTitles.length > 0) {\n const titleWord = processingTitles.length === 1 ? \"title uses\" : \"titles use\";\n statusNote = ` (import in progress, ${processingTitles.length} ${titleWord} earlier dates)`;\n } else if (meta.importInProgress) {\n statusNote = \" (import in progress, using previous day)\";\n } else if (processingTitles.length > 0) {\n const nums = processingTitles.map((t) => t.number).join(\", \");\n statusNote = ` (Title ${nums} processing, using earlier date)`;\n }\n }\n\n const label =\n titleCount === 1\n ? `Downloading eCFR Title ${titles?.[0]} from ${sourceLabel}${dateLabel}${statusNote}`\n : `Downloading ${titleCount} eCFR titles from ${sourceLabel}${dateLabel}${statusNote}`;\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, meta);\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 meta?: EcfrTitlesResponse,\n): Promise<void> {\n const result = await downloadEcfrTitlesFromApi({\n output: outputDir,\n titles,\n date: options.date,\n titlesMeta: meta,\n });\n\n const elapsed = performance.now() - startTime;\n spinner.stop();\n\n // Check if any titles used different dates\n const uniqueDates = new Set(result.files.map((f) => f.asOfDate));\n const dateDisplay =\n uniqueDates.size <= 1\n ? result.asOfDate\n : `${result.asOfDate} (${uniqueDates.size - 1} titles at earlier dates)`;\n\n const fileRows = result.files.map((file) => {\n const row = [\n String(file.titleNumber),\n formatBytes(file.size),\n relative(outputDir, file.path) || file.path,\n ];\n // Show the date if it differs from the primary\n if (file.asOfDate !== result.asOfDate) {\n row.push(file.asOfDate);\n }\n return row;\n });\n\n const summaryRows: [string, string][] = [\n [\"Source\", \"ecfr.gov/api/versioner/v1\"],\n [\"As of date\", dateDisplay],\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 // Include date column only if some titles used different dates\n const hasMultipleDates = uniqueDates.size > 1;\n const headings = hasMultipleDates ? [\"Title\", \"Size\", \"File\", \"Date\"] : [\"Title\", \"Size\", \"File\"];\n\n if (fileRows.length > 0) {\n console.log(dataTable(headings, fileRows));\n }\n\n // Report failures with context\n if (result.failed.length > 0) {\n const processing = result.failed.filter((f) => f.status === 503);\n const other = result.failed.filter((f) => f.status !== 503);\n\n if (processing.length > 0) {\n const nums = processing.map((f) => `Title ${f.titleNumber}`).join(\", \");\n console.log(` ${error(`Unavailable (processing on server): ${nums}`)}`);\n console.log(` The eCFR API cannot serve these titles while an import is in progress.`);\n console.log(\n ` Re-run this command later to download them. Existing local files (if any) are preserved.`,\n );\n }\n if (other.length > 0) {\n const nums = other.map((f) => `Title ${f.titleNumber} (${f.status})`).join(\", \");\n console.log(` ${error(`Failed: ${nums}`)}`);\n }\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.9.4",
3
+ "version": "1.10.1",
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.9.4",
58
- "@lexbuild/ecfr": "1.9.4",
59
- "@lexbuild/usc": "1.9.4"
57
+ "@lexbuild/core": "1.10.1",
58
+ "@lexbuild/ecfr": "1.10.1",
59
+ "@lexbuild/usc": "1.10.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^25.3.2",