The download_jlcpcb_database tool paged the community JLCSearch API with an
offset parameter, but that endpoint is a search front-end that ignores offset
and returns the same first 100 parts on every page, so a full catalog download
was impossible.
Add commands/jlcpcb_downloader.py with a layered strategy that reuses prebuilt
catalogs the whole ecosystem already trusts:
- CDFER single-file SQLite (primary; no 7z/zip, reliable on Windows)
- yaqwsx/jlcparts split 7z (fallback; only if a 7z CLI is present)
- official JLCPCB API (optional; cursor pagination, if credentials set)
Conversion reads CDFER's v_components view (or sniffs the largest table for
yaqwsx), C-prefixes integer lcsc, derives library_type from basic/preferred,
maps mfr->mfr_part, and normalizes price JSON to the manager's [{qty,price}]
shape. Rewire _handle_download_jlcpcb_database to use it (closing/reopening the
manager connection so the on-disk db can be rewritten on Windows). Remove the
broken offset loop from jlcsearch.py (client kept for interactive lookups).
Reduce download_jlcpcb.py to a thin CLI wrapper and update the TS tool schema.
Verified end-to-end against live CDFER: 616k parts downloaded + converted in
~40s, FTS search and price-break parsing correct. New unit tests cover the
conversion and source fall-through; no network in tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
CLI to download the JLCPCB parts database into the MCP server's local DB.
|
|
|
|
This is a thin wrapper around ``commands.jlcpcb_downloader`` (the same code the
|
|
``download_jlcpcb_database`` MCP tool uses). It downloads a prebuilt catalog and
|
|
converts it into ``jlcpcb_parts.db``:
|
|
|
|
CDFER single-file SQLite (primary, no 7z) ->
|
|
yaqwsx split-7z (fallback, needs 7z CLI) ->
|
|
official JLCPCB API (optional, if JLCPCB_APP_ID/API_KEY/API_SECRET set)
|
|
|
|
Usage:
|
|
python download_jlcpcb.py [--source cdfer|yaqwsx|official] [--force]
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent / "python"))
|
|
|
|
from commands import jlcpcb_downloader # noqa: E402
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Download the JLCPCB parts database")
|
|
parser.add_argument(
|
|
"--source",
|
|
choices=["cdfer", "yaqwsx", "official"],
|
|
default=None,
|
|
help="Force a single source (default: try cdfer, then yaqwsx, then official)",
|
|
)
|
|
parser.add_argument(
|
|
"--force", action="store_true", help="Re-download even if a database already exists"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
print("=" * 60)
|
|
print("JLCPCB Parts Database Downloader")
|
|
print("=" * 60)
|
|
start = time.time()
|
|
|
|
result = jlcpcb_downloader.download_database(
|
|
force=args.force,
|
|
prefer_source=args.source,
|
|
progress=lambda msg: print(f" {msg}"),
|
|
)
|
|
|
|
if not result.get("success"):
|
|
print(f"\nERROR: {result.get('message', 'download failed')}")
|
|
for err in result.get("errors", []):
|
|
print(f" - {err}")
|
|
sys.exit(1)
|
|
|
|
elapsed = time.time() - start
|
|
print(f"\nDatabase ready: {result['db_path']}")
|
|
print(f" Source: {result['source']}")
|
|
if result.get("catalog_last_modified"):
|
|
print(f" Catalog dated: {result['catalog_last_modified']}")
|
|
print(f" Total parts: {result['total_parts']:,}")
|
|
print(f" Basic parts: {result['basic_parts']:,}")
|
|
print(f" Extended parts: {result['extended_parts']:,}")
|
|
print(f" DB size: {result['db_size_mb']} MB")
|
|
print(f"\nTotal time: {elapsed / 60:.1f} minutes")
|
|
print("Done! Restart the MCP server (/mcp) to use the new database.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|