@lexbuild/cli 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,15 +6,17 @@
6
6
 
7
7
  This package is part of the [LexBuild](https://github.com/chris-c-thomas/lexbuild) monorepo, a tool that converts U.S. legislative XML into structured Markdown optimized for AI, RAG pipelines, and semantic search. See the monorepo for full documentation, architecture details, and contribution guidelines.
8
8
 
9
- It provides the CLI entry point for downloading and converting legal and civic texts. Currently supports the [United States Code](https://uscode.house.gov/) via [`@lexbuild/usc`](https://www.npmjs.com/package/@lexbuild/usc), with additional sources (CFR, state statutes) planned.
9
+ It provides the CLI entry point for downloading and converting legal and civic texts. Built on [`@lexbuild/core`](https://www.npmjs.com/package/@lexbuild/core) for shared parsing and rendering infrastructure, and also [`@lexbuild/usc`](https://www.npmjs.com/package/@lexbuild/usc) for [United States Code](https://uscode.house.gov/) support. Implementing support for additional sources (CFR, state statutes) is planned.
10
10
 
11
11
  ## Install
12
12
 
13
+ Install globally
14
+
13
15
  ```bash
14
16
  npm install -g @lexbuild/cli
15
17
  ```
16
18
 
17
- Or run directly with npx:
19
+ Run directly with npx:
18
20
 
19
21
  ```bash
20
22
  npx @lexbuild/cli download --all
@@ -62,6 +64,7 @@ lexbuild convert --titles 1 # By title number
62
64
  lexbuild convert --all # All downloaded titles
63
65
  lexbuild convert ./downloads/usc/xml/usc01.xml # Direct file path
64
66
  lexbuild convert --titles 1 -g chapter # Chapter-level output
67
+ lexbuild convert --titles 1 -g title # Title-level output (single file)
65
68
  lexbuild convert --titles 1 --link-style canonical # OLRC website links
66
69
  lexbuild convert --titles 42 --dry-run # Preview without writing
67
70
  ```
@@ -72,7 +75,7 @@ lexbuild convert --titles 42 --dry-run # Preview without writing
72
75
  | `--all` | — | Convert all titles in input directory |
73
76
  | `-i, --input-dir <dir>` | `./downloads/usc/xml` | Input directory for XML files |
74
77
  | `-o, --output <dir>` | `./output` | Output directory |
75
- | `-g, --granularity <level>` | `section` | `section` or `chapter` |
78
+ | `-g, --granularity <level>` | `section` | `section`, `chapter`, or `title` |
76
79
  | `--link-style <style>` | `plaintext` | `plaintext`, `canonical`, or `relative` |
77
80
  | `--no-include-source-credits` | — | Exclude source credits |
78
81
  | `--no-include-notes` | — | Exclude all notes |
@@ -84,18 +87,25 @@ lexbuild convert --titles 42 --dry-run # Preview without writing
84
87
 
85
88
  ## Output
86
89
 
90
+ **Section granularity** (default):
91
+
92
+ ```
93
+ output/usc/title-01/chapter-01/section-1.md
94
+ ```
95
+
96
+ **Chapter granularity** (`-g chapter`):
97
+
98
+ ```
99
+ output/usc/title-01/chapter-01.md
100
+ ```
101
+
102
+ **Title granularity** (`-g title`):
103
+
87
104
  ```
88
- output/usc/
89
- title-01/
90
- README.md
91
- _meta.json
92
- chapter-01/
93
- _meta.json
94
- section-1.md
95
- section-2.md
105
+ output/usc/title-01.md
96
106
  ```
97
107
 
98
- Each section file includes YAML frontmatter (identifier, title, chapter, section, status, source credit) followed by the statutory text with bold inline numbering.
108
+ Each file includes YAML frontmatter (identifier, title, chapter, section, status, source credit) followed by the statutory text with bold inline numbering. Title-level output includes enriched frontmatter with `chapter_count`, `section_count`, and `total_token_estimate`.
99
109
 
100
110
  ## Performance
101
111
 
@@ -103,7 +113,7 @@ The full U.S. Code — all 54 titles, 60,000+ sections, ~85 million estimated to
103
113
 
104
114
  ## Documentation
105
115
 
106
- - [Monorepo README](https://github.com/chris-c-thomas/lexbuild#readme)
116
+ - [LexBuild Monorepo](https://github.com/chris-c-thomas/lexbuild)
107
117
  - [Architecture](https://github.com/chris-c-thomas/lexbuild/blob/main/docs/architecture.md)
108
118
  - [Output Format](https://github.com/chris-c-thomas/lexbuild/blob/main/docs/output-format.md)
109
119
  - [XML Element Reference](https://github.com/chris-c-thomas/lexbuild/blob/main/docs/xml-element-reference.md)
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command as Command3 } from "commander";
6
6
 
7
7
  // src/commands/convert.ts
8
8
  import chalk2 from "chalk";
9
- import { Command } from "commander";
9
+ import { Command, Option } from "commander";
10
10
  import { existsSync, readdirSync } from "fs";
11
11
  import { basename, dirname, join, relative, resolve } from "path";
12
12
  import { convertTitle } from "@lexbuild/usc";
@@ -256,15 +256,31 @@ async function convertSingleFile(inputPath, outputPath, options, spinnerLabel) {
256
256
  process.exit(1);
257
257
  }
258
258
  }
259
- var convertCommand = new Command("convert").description("Convert USC XML file(s) to Markdown").argument("[input]", "Path to a USC XML file").option("-o, --output <dir>", "Output directory", "./output").option("--titles <spec>", "Title(s) to convert (e.g. 1, 1-5, 1,3,8, 1-5,8,11)").option("--all", "Convert all downloaded titles found in --input-dir", false).option("-i, --input-dir <dir>", "Directory containing USC XML files", "./downloads/usc/xml").option(
260
- "-g, --granularity <level>",
261
- 'Output granularity: "section" (one file per section) or "chapter" (sections inline)',
262
- "section"
263
- ).option(
264
- "--link-style <style>",
265
- 'Cross-reference link style: "relative", "canonical", or "plaintext"',
266
- "plaintext"
267
- ).option("--include-source-credits", "Include source credit annotations", true).option("--no-include-source-credits", "Exclude source credit annotations").option("--include-notes", "Include all notes (default)", true).option("--no-include-notes", "Exclude all notes").option("--include-editorial-notes", "Include editorial notes only", false).option("--include-statutory-notes", "Include statutory notes only", false).option("--include-amendments", "Include amendment history notes only", false).option("--dry-run", "Parse and report structure without writing files", false).option("-v, --verbose", "Enable verbose logging", false).action(async (input, options) => {
259
+ var convertCommand = new Command("convert").description("Convert USC XML file(s) to Markdown").argument("[input]", "Path to a USC XML file").option("-o, --output <dir>", "Output directory", "./output").option("--titles <spec>", "Title(s) to convert: 1, 1-5, or 1-5,8,11").option("--all", "Convert all downloaded titles found in --input-dir", false).option("-i, --input-dir <dir>", "Directory containing USC XML files", "./downloads/usc/xml").addOption(
260
+ new Option("-g, --granularity <level>", "Output granularity: section, chapter, or title").choices(["section", "chapter", "title"]).default("section")
261
+ ).addOption(
262
+ new Option("--link-style <style>", "Link style: relative, canonical, or plaintext").choices(["relative", "canonical", "plaintext"]).default("plaintext")
263
+ ).option("--include-source-credits", "Include source credit annotations", true).option("--no-include-source-credits", "Exclude source credit annotations").option("--include-notes", "Include all notes (default)", true).option("--no-include-notes", "Exclude all notes").option("--include-editorial-notes", "Include editorial notes only", false).option("--include-statutory-notes", "Include statutory notes only", false).option("--include-amendments", "Include amendment history notes only", false).option("--dry-run", "Parse and report structure without writing files", false).option("-v, --verbose", "Enable verbose logging", false).addHelpText(
264
+ "after",
265
+ `
266
+ Input modes (use exactly one):
267
+ <input> Convert a single XML file
268
+ --titles Convert specific titles by number
269
+ --all Convert all titles in --input-dir
270
+
271
+ Granularity:
272
+ section One .md file per section (default)
273
+ chapter One .md file per chapter, sections inlined
274
+ title One .md file per title, entire hierarchy inlined
275
+
276
+ Examples:
277
+ $ lexbuild convert --titles 1 Convert Title 1
278
+ $ lexbuild convert --titles 1-5,8,11 Convert a mix of titles
279
+ $ lexbuild convert --all -g chapter All titles, chapter-level
280
+ $ lexbuild convert --titles 26 -g title Title 26 as a single file
281
+ $ lexbuild convert --all --dry-run Preview stats only
282
+ $ lexbuild convert ./downloads/usc/xml/usc01.xml -o ./out`
283
+ ).action(async (input, options) => {
268
284
  const modeCount = [input, options.titles, options.all].filter(Boolean).length;
269
285
  if (modeCount === 0) {
270
286
  console.error(
@@ -376,10 +392,16 @@ var convertCommand = new Command("convert").description("Convert USC XML file(s)
376
392
  import { Command as Command2 } from "commander";
377
393
  import { relative as relative2, resolve as resolve2 } from "path";
378
394
  import { downloadTitles, CURRENT_RELEASE_POINT } from "@lexbuild/usc";
379
- var downloadCommand = new Command2("download").description("Download U.S. Code XML from OLRC").option("-o, --output <dir>", "Download directory", "./downloads/usc/xml").option("--titles <spec>", "Title(s) to download (e.g. 1, 1-5, 1,3,8, 1-5,8,11)").option("--all", "Download all 54 titles", false).option(
380
- "--release-point <id>",
381
- `Release point identifier (default: ${CURRENT_RELEASE_POINT})`,
382
- CURRENT_RELEASE_POINT
395
+ var downloadCommand = new Command2("download").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(
396
+ "after",
397
+ `
398
+ Examples:
399
+ $ lexbuild download --all Download all 54 titles
400
+ $ lexbuild download --titles 1 Download Title 1 only
401
+ $ lexbuild download --titles 1-5,8,11 Download specific titles
402
+ $ lexbuild download --all -o ./my-xml Custom output directory
403
+
404
+ Source: https://uscode.house.gov/download/download.shtml`
383
405
  ).action(async (options) => {
384
406
  if (!options.titles && !options.all) {
385
407
  console.error(error("Specify --titles <spec> or --all (e.g. --titles 1-5,8,11)"));
@@ -446,7 +468,17 @@ var downloadCommand = new Command2("download").description("Download U.S. Code X
446
468
  var require2 = createRequire(import.meta.url);
447
469
  var pkg = require2("../package.json");
448
470
  var program = new Command3();
449
- program.name("lexbuild").description("Convert U.S. legislative XML (USLM) to structured Markdown for AI/RAG ingestion").version(pkg.version);
471
+ program.name("lexbuild").description("Convert U.S. legislative XML (USLM) to structured Markdown for AI/RAG ingestion").version(pkg.version).addHelpText(
472
+ "after",
473
+ `
474
+ Quick start:
475
+ $ lexbuild download --all Download all 54 titles from OLRC
476
+ $ lexbuild convert --all Convert all downloaded titles
477
+ $ lexbuild convert --titles 1 Convert Title 1 only
478
+ $ lexbuild convert --titles 1 -g title Whole title as a single file
479
+
480
+ Documentation: https://github.com/chris-c-thomas/lexbuild`
481
+ );
450
482
  program.addCommand(convertCommand);
451
483
  program.addCommand(downloadCommand);
452
484
  program.parse();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/convert.ts","../src/ui.ts","../src/parse-titles.ts","../src/commands/download.ts"],"sourcesContent":["/** lexbuild CLI — Convert U.S. legislative XML to structured Markdown */\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { convertCommand } from \"./commands/convert.js\";\nimport { downloadCommand } from \"./commands/download.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. legislative XML (USLM) to structured Markdown for AI/RAG ingestion\")\n .version(pkg.version);\n\nprogram.addCommand(convertCommand);\nprogram.addCommand(downloadCommand);\n\nprogram.parse();\n","/**\n * `lexbuild convert` command — converts USC XML files to Markdown.\n */\n\nimport chalk from \"chalk\";\nimport { Command } 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\";\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 [\"Sections\", formatNumber(result.sectionsWritten)],\n [\"Chapters\", formatNumber(result.chapterCount)],\n [\"Est. Tokens\", formatNumber(result.totalTokenEstimate)],\n ];\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 convertCommand = new Command(\"convert\")\n .description(\"Convert USC 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 (e.g. 1, 1-5, 1,3,8, 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 .option(\n \"-g, --granularity <level>\",\n 'Output granularity: \"section\" (one file per section) or \"chapter\" (sections inline)',\n \"section\",\n )\n .option(\n \"--link-style <style>\",\n 'Cross-reference link style: \"relative\", \"canonical\", or \"plaintext\"',\n \"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 .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\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 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 // Totals row\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 console.log(\n dataTable([\"Title\", \"Name\", \"Chapters\", \"Sections\", \"Tokens\", \"Duration\"], tableRows),\n );\n\n // Footer\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n console.log(\n `\\n ${success(`Converted ${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord}) in ${formatDuration(totalElapsed)}`)}`,\n );\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): 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);\n validateTitleNumber(end, input);\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);\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 (1-54). */\nfunction validateTitleNumber(num: number, fullInput: string): void {\n if (num < 1 || num > 54) {\n throw new Error(`Title number ${num} out of range (must be 1-54) in: \"${fullInput}\"`);\n }\n}\n","/**\n * `lexbuild download` 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 downloadCommand = new Command(\"download\")\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 (e.g. 1, 1-5, 1,3,8, 1-5,8,11)\")\n .option(\"--all\", \"Download all 54 titles\", false)\n .option(\n \"--release-point <id>\",\n `Release point identifier (default: ${CURRENT_RELEASE_POINT})`,\n CURRENT_RELEASE_POINT,\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"],"mappings":"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","names":["Command","chalk","inputDir","chalk","Command","relative","resolve","Command","resolve","relative","require","Command"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/convert.ts","../src/ui.ts","../src/parse-titles.ts","../src/commands/download.ts"],"sourcesContent":["/** lexbuild CLI — Convert U.S. legislative XML to structured Markdown */\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { convertCommand } from \"./commands/convert.js\";\nimport { downloadCommand } from \"./commands/download.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. legislative XML (USLM) to structured Markdown for AI/RAG ingestion\")\n .version(pkg.version)\n .addHelpText(\n \"after\",\n `\nQuick start:\n $ lexbuild download --all Download all 54 titles from OLRC\n $ lexbuild convert --all Convert all downloaded titles\n $ lexbuild convert --titles 1 Convert Title 1 only\n $ lexbuild convert --titles 1 -g title Whole title as a single file\n\nDocumentation: https://github.com/chris-c-thomas/lexbuild`,\n );\n\nprogram.addCommand(convertCommand);\nprogram.addCommand(downloadCommand);\n\nprogram.parse();\n","/**\n * `lexbuild convert` 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 [\"Sections\", formatNumber(result.sectionsWritten)],\n [\"Chapters\", formatNumber(result.chapterCount)],\n [\"Est. Tokens\", formatNumber(result.totalTokenEstimate)],\n ];\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 convertCommand = new Command(\"convert\")\n .description(\"Convert USC 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 --titles 1 Convert Title 1\n $ lexbuild convert --titles 1-5,8,11 Convert a mix of titles\n $ lexbuild convert --all -g chapter All titles, chapter-level\n $ lexbuild convert --titles 26 -g title Title 26 as a single file\n $ lexbuild convert --all --dry-run Preview stats only\n $ lexbuild convert ./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\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 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 // Totals row\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 console.log(\n dataTable([\"Title\", \"Name\", \"Chapters\", \"Sections\", \"Tokens\", \"Duration\"], tableRows),\n );\n\n // Footer\n const titleWord = totalTitles === 1 ? \"title\" : \"titles\";\n const sectionWord = totalSections === 1 ? \"section\" : \"sections\";\n console.log(\n `\\n ${success(`Converted ${totalTitles} ${titleWord} (${formatNumber(totalSections)} ${sectionWord}) in ${formatDuration(totalElapsed)}`)}`,\n );\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): 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);\n validateTitleNumber(end, input);\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);\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 (1-54). */\nfunction validateTitleNumber(num: number, fullInput: string): void {\n if (num < 1 || num > 54) {\n throw new Error(`Title number ${num} out of range (must be 1-54) in: \"${fullInput}\"`);\n }\n}\n","/**\n * `lexbuild download` 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 downloadCommand = new Command(\"download\")\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 --all Download all 54 titles\n $ lexbuild download --titles 1 Download Title 1 only\n $ lexbuild download --titles 1-5,8,11 Download specific titles\n $ lexbuild download --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"],"mappings":"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","names":["Command","chalk","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.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Compiler for legal and civic texts. Converts disparate statutory data into structured formats optimized for AI, RAG, and semantic search.",
5
5
  "author": "Chris Thomas",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "ora": "^8.2.0",
55
55
  "pino": "^9.6.0",
56
56
  "pino-pretty": "^13.0.0",
57
- "@lexbuild/core": "1.0.5",
58
- "@lexbuild/usc": "1.0.5"
57
+ "@lexbuild/core": "1.1.0",
58
+ "@lexbuild/usc": "1.1.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^25.3.2",