@jlcpcb/cli 0.1.1 → 0.2.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/dist/index.js CHANGED
@@ -34086,6 +34086,10 @@ import { existsSync as existsSync3 } from "fs";
34086
34086
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
34087
34087
  import { homedir as homedir2, platform as platform2 } from "os";
34088
34088
  import { join as join4 } from "path";
34089
+ import { createServer } from "http";
34090
+ import { readFileSync } from "fs";
34091
+ import { join as join5, dirname as dirname3 } from "path";
34092
+ import { fileURLToPath } from "url";
34089
34093
  var __defProp2 = Object.defineProperty;
34090
34094
  var __export2 = (target, all) => {
34091
34095
  for (var name in all)
@@ -34693,7 +34697,8 @@ var DEFAULT_LIBRARY_DESCRIPTION = "JLC-MCP Component Library (LCSC/EasyEDA)";
34693
34697
  var init_lib_table = () => {};
34694
34698
  var exports_global_lib_table = {};
34695
34699
  __export2(exports_global_lib_table, {
34696
- ensureGlobalLibraryTables: () => ensureGlobalLibraryTables
34700
+ ensureGlobalLibraryTables: () => ensureGlobalLibraryTables,
34701
+ ensureGlobalEasyEDALibrary: () => ensureGlobalEasyEDALibrary
34697
34702
  });
34698
34703
  function detectKicadVersion() {
34699
34704
  const home = homedir();
@@ -34870,6 +34875,51 @@ async function ensureLibraryStubs(version) {
34870
34875
  }
34871
34876
  return { symbolsCreated, directoriesCreated };
34872
34877
  }
34878
+ function getEasyEDASymbolLibUri() {
34879
+ return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${EASYEDA_SYMBOL_LIBRARY_NAME}`;
34880
+ }
34881
+ function getEasyEDAFootprintLibUri() {
34882
+ return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/footprints/${EASYEDA_FOOTPRINT_LIBRARY_NAME}`;
34883
+ }
34884
+ async function ensureGlobalEasyEDALibrary() {
34885
+ const version = detectKicadVersion();
34886
+ const configDir = getKicadConfigDir(version);
34887
+ await mkdir2(configDir, { recursive: true });
34888
+ const symTablePath = join3(configDir, "sym-lib-table");
34889
+ if (existsSync2(symTablePath)) {
34890
+ let content = await readFile3(symTablePath, "utf-8");
34891
+ if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
34892
+ const uri = getEasyEDASymbolLibUri();
34893
+ content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "sym", EASYEDA_LIBRARY_DESCRIPTION);
34894
+ await writeFile3(symTablePath, content, "utf-8");
34895
+ }
34896
+ } else {
34897
+ const uri = getEasyEDASymbolLibUri();
34898
+ const content = `(sym_lib_table
34899
+ (version 7)
34900
+ (lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
34901
+ )
34902
+ `;
34903
+ await writeFile3(symTablePath, content, "utf-8");
34904
+ }
34905
+ const fpTablePath = join3(configDir, "fp-lib-table");
34906
+ if (existsSync2(fpTablePath)) {
34907
+ let content = await readFile3(fpTablePath, "utf-8");
34908
+ if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
34909
+ const uri = getEasyEDAFootprintLibUri();
34910
+ content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "fp", EASYEDA_LIBRARY_DESCRIPTION);
34911
+ await writeFile3(fpTablePath, content, "utf-8");
34912
+ }
34913
+ } else {
34914
+ const uri = getEasyEDAFootprintLibUri();
34915
+ const content = `(fp_lib_table
34916
+ (version 7)
34917
+ (lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
34918
+ )
34919
+ `;
34920
+ await writeFile3(fpTablePath, content, "utf-8");
34921
+ }
34922
+ }
34873
34923
  async function ensureGlobalLibraryTables() {
34874
34924
  const errors2 = [];
34875
34925
  const version = detectKicadVersion();
@@ -34922,6 +34972,10 @@ var LIBRARY_PREFIX2 = "JLC-MCP";
34922
34972
  var LIBRARY_NAMESPACE = "jlc_mcp";
34923
34973
  var KICAD_3RD_PARTY_VAR = "${KICAD9_3RD_PARTY}";
34924
34974
  var LIBRARY_DESCRIPTION = "Autogenerated by JLC-MCP";
34975
+ var EASYEDA_LIBRARY_NAME = "EasyEDA";
34976
+ var EASYEDA_SYMBOL_LIBRARY_NAME = "EasyEDA.kicad_sym";
34977
+ var EASYEDA_FOOTPRINT_LIBRARY_NAME = "EasyEDA.pretty";
34978
+ var EASYEDA_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library";
34925
34979
  var init_global_lib_table = __esm2(() => {
34926
34980
  init_category_router();
34927
34981
  init_lib_table();
@@ -39658,7 +39712,7 @@ function parseFootprintShapes(shapes) {
39658
39712
  }
39659
39713
  }
39660
39714
  }
39661
- const type = pads.some((p) => p.holeRadius > 0) ? "tht" : "smd";
39715
+ const type = pads.some((p) => p.holeRadius > 0 || p.shape === "POLYGON" && p.isPlated) ? "tht" : "smd";
39662
39716
  return {
39663
39717
  name: "Unknown",
39664
39718
  type,
@@ -41722,8 +41776,8 @@ var KI_PAD_LAYER_SMD = {
41722
41776
  11: '"*.Cu" "*.Paste" "*.Mask"'
41723
41777
  };
41724
41778
  var KI_PAD_LAYER_THT = {
41725
- 1: '"F.Cu" "F.Mask"',
41726
- 2: '"B.Cu" "B.Mask"',
41779
+ 1: '"*.Cu" "*.Mask"',
41780
+ 2: '"*.Cu" "*.Mask"',
41727
41781
  11: '"*.Cu" "*.Mask"'
41728
41782
  };
41729
41783
  var KI_PAD_SHAPE = {
@@ -41933,15 +41987,20 @@ class FootprintConverter {
41933
41987
  `;
41934
41988
  return output;
41935
41989
  }
41936
- generatePolygonPad(pad, origin, layers) {
41990
+ generatePolygonPad(pad, origin, _layers) {
41937
41991
  const x = convertX(pad.centerX, origin.x);
41938
41992
  const y = convertY(pad.centerY, origin.y);
41939
41993
  const points = parsePoints(pad.points);
41940
41994
  if (points.length < 3) {
41941
41995
  return this.generatePad({ ...pad, shape: "RECT", points: "" }, origin);
41942
41996
  }
41943
- const isSmd = pad.holeRadius === 0;
41997
+ let holeRadius = pad.holeRadius;
41998
+ if (holeRadius === 0 && pad.isPlated && pad.shape === "POLYGON") {
41999
+ holeRadius = this.calculateDrillRadiusFromPolygon(points, pad.centerX, pad.centerY);
42000
+ }
42001
+ const isSmd = holeRadius === 0;
41944
42002
  const padType = isSmd ? "smd" : "thru_hole";
42003
+ const layers = getPadLayers(pad.layerId, isSmd);
41945
42004
  const polyPoints = points.map((p) => ({
41946
42005
  x: roundTo(toMM(p.x - pad.centerX), 2),
41947
42006
  y: roundTo(toMM(p.y - pad.centerY), 2)
@@ -41953,7 +42012,7 @@ class FootprintConverter {
41953
42012
  output += ` (size 0.01 0.01)
41954
42013
  `;
41955
42014
  if (!isSmd) {
41956
- const drillDiameter = roundTo(toMM(pad.holeRadius * 2), 4);
42015
+ const drillDiameter = roundTo(toMM(holeRadius * 2), 4);
41957
42016
  if (pad.holeLength && pad.holeLength > 0) {
41958
42017
  const holeH = roundTo(toMM(pad.holeLength), 4);
41959
42018
  output += ` (drill oval ${drillDiameter} ${holeH})
@@ -41987,6 +42046,25 @@ class FootprintConverter {
41987
42046
  `;
41988
42047
  return output;
41989
42048
  }
42049
+ calculateDrillRadiusFromPolygon(points, centerX, centerY) {
42050
+ if (points.length < 3)
42051
+ return 0;
42052
+ let minX = Infinity, maxX = -Infinity;
42053
+ let minY = Infinity, maxY = -Infinity;
42054
+ for (const pt of points) {
42055
+ const relX = pt.x - centerX;
42056
+ const relY = pt.y - centerY;
42057
+ minX = Math.min(minX, relX);
42058
+ maxX = Math.max(maxX, relX);
42059
+ minY = Math.min(minY, relY);
42060
+ maxY = Math.max(maxY, relY);
42061
+ }
42062
+ const width = maxX - minX;
42063
+ const height = maxY - minY;
42064
+ const minDim = Math.min(width, height);
42065
+ const drillDiameter = minDim * 0.6;
42066
+ return drillDiameter / 2;
42067
+ }
41990
42068
  generateHole(hole, origin) {
41991
42069
  const x = convertX(hole.centerX, origin.x);
41992
42070
  const y = convertY(hole.centerY, origin.y);
@@ -42113,9 +42191,9 @@ class FootprintConverter {
42113
42191
  const fontSize = roundTo(toMM(text.fontSize), 2);
42114
42192
  const rotation = text.rotation || 0;
42115
42193
  let justify = "";
42116
- if (x < -0.5) {
42194
+ if (text.type === "L") {
42117
42195
  justify = "left";
42118
- } else if (x > 0.5) {
42196
+ } else if (text.type === "R") {
42119
42197
  justify = "right";
42120
42198
  }
42121
42199
  return ` (fp_text user "${this.escapeString(text.text)}"
@@ -42132,6 +42210,9 @@ ${justify ? ` (justify ${justify})
42132
42210
  `;
42133
42211
  }
42134
42212
  generateSolidRegion(region, origin) {
42213
+ if (region.layerId === 99 || region.layerId === 100 || region.layerId === 101) {
42214
+ return "";
42215
+ }
42135
42216
  const layer = getLayer(region.layerId);
42136
42217
  const points = this.parseSvgPathToPoints(region.path, origin);
42137
42218
  if (points.length < 3)
@@ -42164,15 +42245,111 @@ ${justify ? ` (justify ${justify})
42164
42245
  }
42165
42246
  parseSvgPathToPoints(path, origin) {
42166
42247
  const points = [];
42167
- const commandRegex = /([ML])\s*([\d.-]+)[,\s]+([\d.-]+)/gi;
42248
+ let currentX = 0, currentY = 0;
42249
+ let startX = 0, startY = 0;
42250
+ const commandRegex = /([MLHVCSQTAZ])\s*([^MLHVCSQTAZ]*)/gi;
42168
42251
  let match;
42169
42252
  while ((match = commandRegex.exec(path)) !== null) {
42170
- const x = parseFloat(match[2]);
42171
- const y = parseFloat(match[3]);
42172
- points.push({
42173
- x: convertX(x, origin.x),
42174
- y: convertY(y, origin.y)
42175
- });
42253
+ const cmd = match[1].toUpperCase();
42254
+ const args = match[2].trim().split(/[\s,]+/).map(parseFloat).filter((n) => !isNaN(n));
42255
+ switch (cmd) {
42256
+ case "M":
42257
+ if (args.length >= 2) {
42258
+ currentX = args[0];
42259
+ currentY = args[1];
42260
+ if (points.length === 0) {
42261
+ startX = currentX;
42262
+ startY = currentY;
42263
+ }
42264
+ points.push({
42265
+ x: convertX(currentX, origin.x),
42266
+ y: convertY(currentY, origin.y)
42267
+ });
42268
+ }
42269
+ break;
42270
+ case "L":
42271
+ if (args.length >= 2) {
42272
+ currentX = args[0];
42273
+ currentY = args[1];
42274
+ points.push({
42275
+ x: convertX(currentX, origin.x),
42276
+ y: convertY(currentY, origin.y)
42277
+ });
42278
+ }
42279
+ break;
42280
+ case "H":
42281
+ if (args.length >= 1) {
42282
+ currentX = args[0];
42283
+ points.push({
42284
+ x: convertX(currentX, origin.x),
42285
+ y: convertY(currentY, origin.y)
42286
+ });
42287
+ }
42288
+ break;
42289
+ case "V":
42290
+ if (args.length >= 1) {
42291
+ currentY = args[0];
42292
+ points.push({
42293
+ x: convertX(currentX, origin.x),
42294
+ y: convertY(currentY, origin.y)
42295
+ });
42296
+ }
42297
+ break;
42298
+ case "C":
42299
+ if (args.length >= 6) {
42300
+ currentX = args[4];
42301
+ currentY = args[5];
42302
+ points.push({
42303
+ x: convertX(currentX, origin.x),
42304
+ y: convertY(currentY, origin.y)
42305
+ });
42306
+ }
42307
+ break;
42308
+ case "S":
42309
+ if (args.length >= 4) {
42310
+ currentX = args[2];
42311
+ currentY = args[3];
42312
+ points.push({
42313
+ x: convertX(currentX, origin.x),
42314
+ y: convertY(currentY, origin.y)
42315
+ });
42316
+ }
42317
+ break;
42318
+ case "Q":
42319
+ if (args.length >= 4) {
42320
+ currentX = args[2];
42321
+ currentY = args[3];
42322
+ points.push({
42323
+ x: convertX(currentX, origin.x),
42324
+ y: convertY(currentY, origin.y)
42325
+ });
42326
+ }
42327
+ break;
42328
+ case "T":
42329
+ if (args.length >= 2) {
42330
+ currentX = args[0];
42331
+ currentY = args[1];
42332
+ points.push({
42333
+ x: convertX(currentX, origin.x),
42334
+ y: convertY(currentY, origin.y)
42335
+ });
42336
+ }
42337
+ break;
42338
+ case "A":
42339
+ if (args.length >= 7) {
42340
+ currentX = args[5];
42341
+ currentY = args[6];
42342
+ points.push({
42343
+ x: convertX(currentX, origin.x),
42344
+ y: convertY(currentY, origin.y)
42345
+ });
42346
+ }
42347
+ break;
42348
+ case "Z":
42349
+ currentX = startX;
42350
+ currentY = startY;
42351
+ break;
42352
+ }
42176
42353
  }
42177
42354
  return points;
42178
42355
  }
@@ -42340,10 +42517,14 @@ var FOOTPRINT_LIBRARY_NAME = getFootprintDirName();
42340
42517
  var MODELS_3D_NAME = get3DModelsDirName();
42341
42518
  var LIBRARY_NAMESPACE2 = "jlc_mcp";
42342
42519
  var KICAD_VERSIONS3 = ["9.0", "8.0"];
42343
- var EASYEDA_LIBRARY_NAME = "EasyEDA";
42344
- var EASYEDA_SYMBOL_LIBRARY_NAME = "EasyEDA.kicad_sym";
42345
- var EASYEDA_FOOTPRINT_LIBRARY_NAME = "EasyEDA.pretty";
42346
- var EASYEDA_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library";
42520
+ var EASYEDA_LIBRARY_NAME2 = "EasyEDA";
42521
+ var EASYEDA_SYMBOL_LIBRARY_NAME2 = "EasyEDA.kicad_sym";
42522
+ var EASYEDA_FOOTPRINT_LIBRARY_NAME2 = "EasyEDA.pretty";
42523
+ var EASYEDA_LIBRARY_DESCRIPTION2 = "EasyEDA Community Component Library";
42524
+ var EASYEDA_LOCAL_LIBRARY_NAME = "EasyEDA-local";
42525
+ var EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME = "EasyEDA-local.kicad_sym";
42526
+ var EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME = "EasyEDA-local.pretty";
42527
+ var EASYEDA_LOCAL_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library (Project-local)";
42347
42528
  function detectKicadVersion2() {
42348
42529
  const home = homedir2();
42349
42530
  const baseDir = join4(home, "Documents", "KiCad");
@@ -42484,10 +42665,7 @@ function createLibraryService() {
42484
42665
  return {
42485
42666
  async install(id, options = {}) {
42486
42667
  const isCommunityComponent = !isLcscId2(id);
42487
- if (isCommunityComponent && !options.projectPath) {
42488
- throw new Error("EasyEDA community components require projectPath for local storage");
42489
- }
42490
- const isGlobal = !isCommunityComponent && !options.projectPath;
42668
+ const isGlobal = !options.projectPath;
42491
42669
  const paths = isGlobal ? getGlobalLibraryPaths() : getProjectLibraryPaths(options.projectPath);
42492
42670
  let component = null;
42493
42671
  if (isLcscId2(id)) {
@@ -42530,13 +42708,27 @@ function createLibraryService() {
42530
42708
  let category;
42531
42709
  let modelPath;
42532
42710
  if (isCommunityComponent) {
42533
- const librariesDir = join4(options.projectPath, "libraries");
42534
- const symbolsDir = join4(librariesDir, "symbols");
42535
- footprintDir = join4(librariesDir, "footprints", EASYEDA_FOOTPRINT_LIBRARY_NAME);
42536
- models3dDir = join4(librariesDir, "3dmodels", "EasyEDA.3dshapes");
42711
+ const libName = isGlobal ? EASYEDA_LIBRARY_NAME2 : EASYEDA_LOCAL_LIBRARY_NAME;
42712
+ const symLibFile = isGlobal ? EASYEDA_SYMBOL_LIBRARY_NAME2 : EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME;
42713
+ const fpLibDir = isGlobal ? EASYEDA_FOOTPRINT_LIBRARY_NAME2 : EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME;
42714
+ const libDesc = isGlobal ? EASYEDA_LIBRARY_DESCRIPTION2 : EASYEDA_LOCAL_LIBRARY_DESCRIPTION;
42715
+ const models3dDirName = isGlobal ? "EasyEDA.3dshapes" : "EasyEDA-local.3dshapes";
42716
+ let symbolsDir;
42717
+ let easyedaModelsDir;
42718
+ if (isGlobal) {
42719
+ symbolsDir = paths.symbolsDir;
42720
+ footprintDir = join4(paths.footprintsDir, fpLibDir);
42721
+ easyedaModelsDir = join4(paths.models3dDir, models3dDirName);
42722
+ } else {
42723
+ const librariesDir = join4(options.projectPath, "libraries");
42724
+ symbolsDir = join4(librariesDir, "symbols");
42725
+ footprintDir = join4(librariesDir, "footprints", fpLibDir);
42726
+ easyedaModelsDir = join4(librariesDir, "3dmodels", models3dDirName);
42727
+ }
42728
+ models3dDir = easyedaModelsDir;
42537
42729
  await ensureDir(symbolsDir);
42538
42730
  await ensureDir(footprintDir);
42539
- symbolFile = join4(symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME);
42731
+ symbolFile = join4(symbolsDir, symLibFile);
42540
42732
  symbolName = component.info.name.replace(/[^a-zA-Z0-9_-]/g, "_");
42541
42733
  const include3d = options.include3d ?? true;
42542
42734
  let modelRelativePath;
@@ -42547,21 +42739,30 @@ function createLibraryService() {
42547
42739
  const modelFilename = `${symbolName}.step`;
42548
42740
  modelPath = join4(models3dDir, modelFilename);
42549
42741
  await writeBinary(modelPath, model);
42550
- modelRelativePath = `\${KIPRJMOD}/libraries/3dmodels/EasyEDA.3dshapes/${modelFilename}`;
42742
+ if (isGlobal) {
42743
+ modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${models3dDirName}/${modelFilename}`;
42744
+ } else {
42745
+ modelRelativePath = `\${KIPRJMOD}/libraries/3dmodels/${models3dDirName}/${modelFilename}`;
42746
+ }
42551
42747
  }
42552
42748
  }
42553
42749
  const footprint = footprintConverter.convert(component, {
42554
- libraryName: EASYEDA_LIBRARY_NAME,
42750
+ libraryName: libName,
42555
42751
  include3DModel: !!modelRelativePath,
42556
42752
  modelPath: modelRelativePath
42557
42753
  });
42558
42754
  footprintPath = join4(footprintDir, `${symbolName}.kicad_mod`);
42559
- footprintRef = `${EASYEDA_LIBRARY_NAME}:${symbolName}`;
42755
+ footprintRef = `${libName}:${symbolName}`;
42560
42756
  await writeText(footprintPath, footprint);
42561
42757
  component.info.package = footprintRef;
42562
- await ensureSymLibTable(options.projectPath, symbolFile, EASYEDA_LIBRARY_NAME, EASYEDA_LIBRARY_DESCRIPTION);
42563
- await ensureFpLibTable(options.projectPath, footprintDir, EASYEDA_LIBRARY_NAME, EASYEDA_LIBRARY_DESCRIPTION);
42564
- symbolRef = `${EASYEDA_LIBRARY_NAME}:${symbolName}`;
42758
+ if (isGlobal) {
42759
+ const { ensureGlobalEasyEDALibrary: ensureGlobalEasyEDALibrary2 } = await Promise.resolve().then(() => (init_global_lib_table(), exports_global_lib_table));
42760
+ await ensureGlobalEasyEDALibrary2();
42761
+ } else {
42762
+ await ensureSymLibTable(options.projectPath, symbolFile, libName, libDesc);
42763
+ await ensureFpLibTable(options.projectPath, footprintDir, libName, libDesc);
42764
+ }
42765
+ symbolRef = `${libName}:${symbolName}`;
42565
42766
  } else {
42566
42767
  category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
42567
42768
  const symbolLibraryFilename = getLibraryFilename(category);
@@ -42608,7 +42809,7 @@ function createLibraryService() {
42608
42809
  if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
42609
42810
  if (options.force) {
42610
42811
  symbolContent = symbolConverter.replaceInLibrary(existingContent, component, {
42611
- libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME : undefined,
42812
+ libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
42612
42813
  symbolName: isCommunityComponent ? symbolName : undefined
42613
42814
  });
42614
42815
  symbolAction = "replaced";
@@ -42618,14 +42819,14 @@ function createLibraryService() {
42618
42819
  }
42619
42820
  } else {
42620
42821
  symbolContent = symbolConverter.appendToLibrary(existingContent, component, {
42621
- libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME : undefined,
42822
+ libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
42622
42823
  symbolName: isCommunityComponent ? symbolName : undefined
42623
42824
  });
42624
42825
  symbolAction = "appended";
42625
42826
  }
42626
42827
  } else {
42627
42828
  symbolContent = symbolConverter.convert(component, {
42628
- libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME : undefined,
42829
+ libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
42629
42830
  symbolName: isCommunityComponent ? symbolName : undefined
42630
42831
  });
42631
42832
  symbolAction = "created";
@@ -42750,12 +42951,243 @@ function createLibraryService() {
42750
42951
  fpLibTable: fpLibTablePath
42751
42952
  }
42752
42953
  };
42954
+ },
42955
+ async isEasyEDAInstalled(componentName) {
42956
+ const paths = getGlobalLibraryPaths();
42957
+ const easyedaLibPath = join4(paths.symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME2);
42958
+ if (!existsSync3(easyedaLibPath)) {
42959
+ return false;
42960
+ }
42961
+ try {
42962
+ const content = await readFile4(easyedaLibPath, "utf-8");
42963
+ const sanitizedName = componentName.replace(/[^a-zA-Z0-9_-]/g, "_");
42964
+ const pattern = new RegExp(`\\(symbol\\s+"${sanitizedName}"`, "m");
42965
+ return pattern.test(content);
42966
+ } catch {
42967
+ return false;
42968
+ }
42753
42969
  }
42754
42970
  };
42755
42971
  }
42756
42972
  init_lib_table();
42757
42973
  init_category_router();
42758
42974
  init_global_lib_table();
42975
+ var logger7 = createLogger("http-routes");
42976
+ var __filename2 = fileURLToPath(import.meta.url);
42977
+ var __dirname2 = dirname3(__filename2);
42978
+ var htmlCache = null;
42979
+ function getHtmlPage() {
42980
+ if (htmlCache)
42981
+ return htmlCache;
42982
+ try {
42983
+ const possiblePaths = [
42984
+ join5(__dirname2, "assets/search.html"),
42985
+ join5(__dirname2, "../dist/assets/search.html"),
42986
+ join5(__dirname2, "../assets/search.html"),
42987
+ join5(__dirname2, "../assets/search-built.html"),
42988
+ join5(process.cwd(), "dist/assets/search.html"),
42989
+ join5(process.cwd(), "packages/core/dist/assets/search.html"),
42990
+ join5(__dirname2, "../../dist/assets/search.html"),
42991
+ join5(__dirname2, "../../../core/dist/assets/search.html"),
42992
+ join5(__dirname2, "../../../../packages/core/dist/assets/search.html"),
42993
+ join5(__dirname2, "../../node_modules/@jlcpcb/core/dist/assets/search.html")
42994
+ ];
42995
+ for (const path of possiblePaths) {
42996
+ try {
42997
+ htmlCache = readFileSync(path, "utf-8");
42998
+ logger7.debug(`Loaded HTML from: ${path}`);
42999
+ return htmlCache;
43000
+ } catch {}
43001
+ }
43002
+ throw new Error("HTML file not found");
43003
+ } catch (error) {
43004
+ logger7.error("Failed to load HTML page:", error);
43005
+ return `<!DOCTYPE html>
43006
+ <html>
43007
+ <head><title>Error</title></head>
43008
+ <body>
43009
+ <h1>Error: Search page not found</h1>
43010
+ <p>The search page has not been built. Run: bun run build</p>
43011
+ </body>
43012
+ </html>`;
43013
+ }
43014
+ }
43015
+ async function handleRequest(req, res) {
43016
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
43017
+ const pathname = url.pathname;
43018
+ logger7.debug(`${req.method} ${pathname}`);
43019
+ if (pathname === "/" || pathname === "/index.html") {
43020
+ const query = url.searchParams.get("q") || undefined;
43021
+ serveHtml(res, query);
43022
+ } else if (pathname === "/api/search") {
43023
+ await handleSearch(url, res);
43024
+ } else if (pathname.startsWith("/api/component/")) {
43025
+ const uuid = pathname.replace("/api/component/", "");
43026
+ await handleComponent(uuid, res);
43027
+ } else if (pathname === "/health") {
43028
+ res.writeHead(200, { "Content-Type": "application/json" });
43029
+ res.end(JSON.stringify({ status: "ok" }));
43030
+ } else {
43031
+ res.writeHead(404, { "Content-Type": "application/json" });
43032
+ res.end(JSON.stringify({ error: "Not found" }));
43033
+ }
43034
+ }
43035
+ function serveHtml(res, initialQuery) {
43036
+ let html = getHtmlPage();
43037
+ if (initialQuery) {
43038
+ const script = `<script>window.__INITIAL_QUERY__ = ${JSON.stringify(initialQuery)};</script>`;
43039
+ html = html.replace("</head>", `${script}</head>`);
43040
+ }
43041
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
43042
+ res.end(html);
43043
+ }
43044
+ async function handleSearch(url, res) {
43045
+ const query = url.searchParams.get("q") || "";
43046
+ const source = url.searchParams.get("source") || "user";
43047
+ const page = parseInt(url.searchParams.get("page") || "1", 10);
43048
+ const limit = parseInt(url.searchParams.get("limit") || "20", 10);
43049
+ if (!query) {
43050
+ res.writeHead(400, { "Content-Type": "application/json" });
43051
+ res.end(JSON.stringify({ error: "Missing query parameter" }));
43052
+ return;
43053
+ }
43054
+ try {
43055
+ const allResults = await easyedaCommunityClient.search({
43056
+ query,
43057
+ source,
43058
+ limit: Math.min(limit * page + limit, 100)
43059
+ });
43060
+ const startIndex = (page - 1) * limit;
43061
+ const endIndex = startIndex + limit;
43062
+ const results = allResults.slice(startIndex, endIndex);
43063
+ const totalPages = Math.ceil(allResults.length / limit);
43064
+ res.writeHead(200, { "Content-Type": "application/json" });
43065
+ res.end(JSON.stringify({
43066
+ results,
43067
+ pagination: {
43068
+ page,
43069
+ limit,
43070
+ total: allResults.length,
43071
+ totalPages,
43072
+ hasNext: page < totalPages,
43073
+ hasPrev: page > 1
43074
+ }
43075
+ }));
43076
+ } catch (error) {
43077
+ logger7.error("Search error:", error);
43078
+ res.writeHead(500, { "Content-Type": "application/json" });
43079
+ res.end(JSON.stringify({ error: "Search failed" }));
43080
+ }
43081
+ }
43082
+ function communityToComponentData(community) {
43083
+ const cPara = community.symbol.head?.c_para ?? {};
43084
+ const fpCPara = community.footprint.head?.c_para ?? {};
43085
+ return {
43086
+ info: {
43087
+ name: community.title || cPara.name || "Unknown",
43088
+ prefix: cPara.pre || cPara.Prefix || "U",
43089
+ package: fpCPara.package || community.footprint.name,
43090
+ manufacturer: cPara.Manufacturer || cPara.BOM_Manufacturer,
43091
+ description: community.description || cPara.BOM_Manufacturer_Part,
43092
+ category: cPara.package
43093
+ },
43094
+ symbol: community.symbol,
43095
+ footprint: community.footprint,
43096
+ model3d: community.model3d,
43097
+ rawData: community.rawData
43098
+ };
43099
+ }
43100
+ async function handleComponent(uuid, res) {
43101
+ if (!uuid) {
43102
+ res.writeHead(400, { "Content-Type": "application/json" });
43103
+ res.end(JSON.stringify({ error: "Missing UUID" }));
43104
+ return;
43105
+ }
43106
+ try {
43107
+ const component2 = await easyedaCommunityClient.getComponent(uuid);
43108
+ if (!component2) {
43109
+ res.writeHead(404, { "Content-Type": "application/json" });
43110
+ res.end(JSON.stringify({ error: "Component not found" }));
43111
+ return;
43112
+ }
43113
+ const componentData = communityToComponentData(component2);
43114
+ let symbolSexpr = "";
43115
+ let footprintSexpr = "";
43116
+ try {
43117
+ symbolSexpr = symbolConverter.convertToSymbolEntry(componentData);
43118
+ } catch (e) {
43119
+ logger7.warn("Symbol conversion failed:", e);
43120
+ }
43121
+ try {
43122
+ footprintSexpr = footprintConverter.convert(componentData);
43123
+ } catch (e) {
43124
+ logger7.warn("Footprint conversion failed:", e);
43125
+ }
43126
+ res.writeHead(200, { "Content-Type": "application/json" });
43127
+ res.end(JSON.stringify({
43128
+ uuid: component2.uuid,
43129
+ title: component2.title,
43130
+ description: component2.description,
43131
+ owner: component2.owner,
43132
+ symbolSexpr,
43133
+ footprintSexpr,
43134
+ model3d: component2.model3d
43135
+ }));
43136
+ } catch (error) {
43137
+ logger7.error("Component fetch error:", error);
43138
+ res.writeHead(500, { "Content-Type": "application/json" });
43139
+ res.end(JSON.stringify({ error: "Failed to fetch component" }));
43140
+ }
43141
+ }
43142
+ var logger8 = createLogger("http-server");
43143
+ var DEFAULT_PORT = 3847;
43144
+ var serverInstance = null;
43145
+ function startHttpServer(options = {}) {
43146
+ if (serverInstance) {
43147
+ logger8.debug("HTTP server already running");
43148
+ const port2 = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
43149
+ options.onReady?.(`http://localhost:${port2}`);
43150
+ return port2;
43151
+ }
43152
+ const port = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
43153
+ serverInstance = createServer(async (req, res) => {
43154
+ res.setHeader("Access-Control-Allow-Origin", "*");
43155
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
43156
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
43157
+ if (req.method === "OPTIONS") {
43158
+ res.writeHead(204);
43159
+ res.end();
43160
+ return;
43161
+ }
43162
+ try {
43163
+ await handleRequest(req, res);
43164
+ } catch (error) {
43165
+ logger8.error("Request error:", error);
43166
+ res.writeHead(500, { "Content-Type": "application/json" });
43167
+ res.end(JSON.stringify({ error: "Internal server error" }));
43168
+ }
43169
+ });
43170
+ serverInstance.listen(port, () => {
43171
+ const url = `http://localhost:${port}`;
43172
+ logger8.info(`HTTP server listening on ${url}`);
43173
+ options.onReady?.(url);
43174
+ });
43175
+ serverInstance.on("error", (error) => {
43176
+ if (error.code === "EADDRINUSE") {
43177
+ logger8.warn(`Port ${port} already in use, HTTP server not started`);
43178
+ } else {
43179
+ logger8.error("HTTP server error:", error);
43180
+ }
43181
+ });
43182
+ return port;
43183
+ }
43184
+ function stopHttpServer() {
43185
+ if (serverInstance) {
43186
+ serverInstance.close();
43187
+ serverInstance = null;
43188
+ logger8.info("HTTP server stopped");
43189
+ }
43190
+ }
42759
43191
 
42760
43192
  // ../../node_modules/ink/build/render.js
42761
43193
  import { Stream } from "node:stream";
@@ -48995,7 +49427,7 @@ var import_react30 = __toESM(require_react(), 1);
48995
49427
  // ../../node_modules/open/index.js
48996
49428
  import process19 from "node:process";
48997
49429
  import path from "node:path";
48998
- import { fileURLToPath } from "node:url";
49430
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
48999
49431
  import childProcess3 from "node:child_process";
49000
49432
  import fs6, { constants as fsConstants2 } from "node:fs/promises";
49001
49433
 
@@ -49306,8 +49738,8 @@ var is_in_ssh_default = isInSsh;
49306
49738
 
49307
49739
  // ../../node_modules/open/index.js
49308
49740
  var fallbackAttemptSymbol = Symbol("fallbackAttempt");
49309
- var __dirname2 = import.meta.url ? path.dirname(fileURLToPath(import.meta.url)) : "";
49310
- var localXdgOpenPath = path.join(__dirname2, "xdg-open");
49741
+ var __dirname3 = import.meta.url ? path.dirname(fileURLToPath2(import.meta.url)) : "";
49742
+ var localXdgOpenPath = path.join(__dirname3, "xdg-open");
49311
49743
  var { platform: platform5, arch } = process19;
49312
49744
  var tryEachApp = async (apps, opener) => {
49313
49745
  if (apps.length === 0) {
@@ -49451,7 +49883,7 @@ var baseOpen = async (options) => {
49451
49883
  if (app) {
49452
49884
  command = app;
49453
49885
  } else {
49454
- const isBundled = !__dirname2 || __dirname2 === "/";
49886
+ const isBundled = !__dirname3 || __dirname3 === "/";
49455
49887
  let exeLocalXdgOpen = false;
49456
49888
  try {
49457
49889
  await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
@@ -50843,23 +51275,416 @@ function LibrarySetupScreen() {
50843
51275
  }, undefined, true, undefined, this);
50844
51276
  }
50845
51277
 
50846
- // src/app/App.tsx
51278
+ // src/app/screens/EasyEDAInfoScreen.tsx
51279
+ var import_react34 = __toESM(require_react(), 1);
51280
+
51281
+ // src/app/components/EasyEDADetailView.tsx
50847
51282
  var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
51283
+ function truncate4(str, len) {
51284
+ if (!str)
51285
+ return "";
51286
+ return str.length > len ? str.slice(0, len - 1) + "…" : str;
51287
+ }
51288
+ function formatDate(timestamp) {
51289
+ if (!timestamp)
51290
+ return "N/A";
51291
+ const date = new Date(timestamp * 1000);
51292
+ return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
51293
+ }
51294
+ function EasyEDADetailView({ component, terminalWidth, isInstalled, statusMessage }) {
51295
+ const isWide2 = terminalWidth >= 80;
51296
+ const labelWidth = 16;
51297
+ const colWidth = isWide2 ? Math.floor((terminalWidth - 4) / 2) : terminalWidth - 2;
51298
+ const valueWidth = colWidth - labelWidth - 1;
51299
+ const description = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51300
+ flexDirection: "column",
51301
+ marginBottom: 1,
51302
+ width: terminalWidth - 2,
51303
+ children: [
51304
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51305
+ dimColor: true,
51306
+ children: "Description"
51307
+ }, undefined, false, undefined, this),
51308
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51309
+ wrap: "wrap",
51310
+ children: component.description || "No description"
51311
+ }, undefined, false, undefined, this)
51312
+ ]
51313
+ }, undefined, true, undefined, this);
51314
+ const partInfo = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51315
+ flexDirection: "column",
51316
+ width: colWidth,
51317
+ children: [
51318
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51319
+ bold: true,
51320
+ underline: true,
51321
+ color: "cyan",
51322
+ children: "Component Info"
51323
+ }, undefined, false, undefined, this),
51324
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51325
+ marginTop: 1,
51326
+ flexDirection: "column",
51327
+ children: [
51328
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51329
+ children: [
51330
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51331
+ dimColor: true,
51332
+ children: "Title".padEnd(labelWidth)
51333
+ }, undefined, false, undefined, this),
51334
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51335
+ bold: true,
51336
+ children: truncate4(component.title || "N/A", valueWidth)
51337
+ }, undefined, false, undefined, this)
51338
+ ]
51339
+ }, undefined, true, undefined, this),
51340
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51341
+ children: [
51342
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51343
+ dimColor: true,
51344
+ children: "UUID".padEnd(labelWidth)
51345
+ }, undefined, false, undefined, this),
51346
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51347
+ color: "cyan",
51348
+ children: truncate4(component.uuid, valueWidth)
51349
+ }, undefined, false, undefined, this)
51350
+ ]
51351
+ }, undefined, true, undefined, this),
51352
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51353
+ children: [
51354
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51355
+ dimColor: true,
51356
+ children: "Package".padEnd(labelWidth)
51357
+ }, undefined, false, undefined, this),
51358
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51359
+ children: truncate4(component.footprint?.name || "N/A", valueWidth)
51360
+ }, undefined, false, undefined, this)
51361
+ ]
51362
+ }, undefined, true, undefined, this),
51363
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51364
+ children: [
51365
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51366
+ dimColor: true,
51367
+ children: "Pins".padEnd(labelWidth)
51368
+ }, undefined, false, undefined, this),
51369
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51370
+ children: component.symbol?.pins?.length || 0
51371
+ }, undefined, false, undefined, this)
51372
+ ]
51373
+ }, undefined, true, undefined, this),
51374
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51375
+ children: [
51376
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51377
+ dimColor: true,
51378
+ children: "Pads".padEnd(labelWidth)
51379
+ }, undefined, false, undefined, this),
51380
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51381
+ children: component.footprint?.pads?.length || 0
51382
+ }, undefined, false, undefined, this)
51383
+ ]
51384
+ }, undefined, true, undefined, this),
51385
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51386
+ children: [
51387
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51388
+ dimColor: true,
51389
+ children: "3D Model".padEnd(labelWidth)
51390
+ }, undefined, false, undefined, this),
51391
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51392
+ color: component.model3d ? "green" : "yellow",
51393
+ children: component.model3d ? "Yes" : "No"
51394
+ }, undefined, false, undefined, this)
51395
+ ]
51396
+ }, undefined, true, undefined, this),
51397
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51398
+ children: [
51399
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51400
+ dimColor: true,
51401
+ children: "Verified".padEnd(labelWidth)
51402
+ }, undefined, false, undefined, this),
51403
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51404
+ color: component.verify ? "green" : "yellow",
51405
+ children: component.verify ? "Yes" : "No"
51406
+ }, undefined, false, undefined, this)
51407
+ ]
51408
+ }, undefined, true, undefined, this)
51409
+ ]
51410
+ }, undefined, true, undefined, this)
51411
+ ]
51412
+ }, undefined, true, undefined, this);
51413
+ const communityInfo = /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51414
+ flexDirection: "column",
51415
+ marginLeft: isWide2 ? 2 : 0,
51416
+ marginTop: isWide2 ? 0 : 1,
51417
+ width: colWidth,
51418
+ children: [
51419
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51420
+ bold: true,
51421
+ underline: true,
51422
+ color: "cyan",
51423
+ children: "Community"
51424
+ }, undefined, false, undefined, this),
51425
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51426
+ marginTop: 1,
51427
+ flexDirection: "column",
51428
+ children: [
51429
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51430
+ children: [
51431
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51432
+ dimColor: true,
51433
+ children: "Owner".padEnd(labelWidth)
51434
+ }, undefined, false, undefined, this),
51435
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51436
+ children: truncate4(component.owner?.nickname || component.owner?.username || "N/A", valueWidth)
51437
+ }, undefined, false, undefined, this)
51438
+ ]
51439
+ }, undefined, true, undefined, this),
51440
+ component.creator && component.creator.uuid !== component.owner?.uuid && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51441
+ children: [
51442
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51443
+ dimColor: true,
51444
+ children: "Creator".padEnd(labelWidth)
51445
+ }, undefined, false, undefined, this),
51446
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51447
+ children: truncate4(component.creator.nickname || component.creator.username || "N/A", valueWidth)
51448
+ }, undefined, false, undefined, this)
51449
+ ]
51450
+ }, undefined, true, undefined, this),
51451
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51452
+ children: [
51453
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51454
+ dimColor: true,
51455
+ children: "Updated".padEnd(labelWidth)
51456
+ }, undefined, false, undefined, this),
51457
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51458
+ children: formatDate(component.updateTime)
51459
+ }, undefined, false, undefined, this)
51460
+ ]
51461
+ }, undefined, true, undefined, this),
51462
+ component.tags && component.tags.length > 0 && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51463
+ children: [
51464
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51465
+ dimColor: true,
51466
+ children: "Tags".padEnd(labelWidth)
51467
+ }, undefined, false, undefined, this),
51468
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51469
+ children: truncate4(component.tags.slice(0, 3).join(", "), valueWidth)
51470
+ }, undefined, false, undefined, this)
51471
+ ]
51472
+ }, undefined, true, undefined, this)
51473
+ ]
51474
+ }, undefined, true, undefined, this)
51475
+ ]
51476
+ }, undefined, true, undefined, this);
51477
+ const footerText = isInstalled ? "R Regenerate • Esc Back" : "Enter Install • Esc Back";
51478
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51479
+ flexDirection: "column",
51480
+ width: "100%",
51481
+ children: [
51482
+ description,
51483
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51484
+ flexDirection: isWide2 ? "row" : "column",
51485
+ width: "100%",
51486
+ children: [
51487
+ partInfo,
51488
+ communityInfo
51489
+ ]
51490
+ }, undefined, true, undefined, this),
51491
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51492
+ marginTop: 1,
51493
+ flexDirection: "column",
51494
+ width: "100%",
51495
+ children: [
51496
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Divider, {
51497
+ width: terminalWidth
51498
+ }, undefined, false, undefined, this),
51499
+ statusMessage && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51500
+ paddingY: 0,
51501
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51502
+ color: statusMessage.startsWith("✓") ? "green" : statusMessage.startsWith("✗") ? "red" : "yellow",
51503
+ children: statusMessage
51504
+ }, undefined, false, undefined, this)
51505
+ }, undefined, false, undefined, this),
51506
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51507
+ paddingY: 0,
51508
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
51509
+ dimColor: true,
51510
+ children: footerText
51511
+ }, undefined, false, undefined, this)
51512
+ }, undefined, false, undefined, this),
51513
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Divider, {
51514
+ width: terminalWidth
51515
+ }, undefined, false, undefined, this)
51516
+ ]
51517
+ }, undefined, true, undefined, this)
51518
+ ]
51519
+ }, undefined, true, undefined, this);
51520
+ }
51521
+
51522
+ // src/app/screens/EasyEDAInfoScreen.tsx
51523
+ var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
51524
+ var componentService4 = createComponentService();
51525
+ var libraryService5 = createLibraryService();
51526
+ function EasyEDAInfoScreen() {
51527
+ const { replace } = useNavigation();
51528
+ const { params } = useCurrentScreen();
51529
+ const { columns: terminalWidth } = useTerminalSize();
51530
+ const [component, setComponent] = import_react34.useState(null);
51531
+ const [isLoading, setIsLoading] = import_react34.useState(true);
51532
+ const [error, setError] = import_react34.useState(null);
51533
+ const [isInstalling, setIsInstalling] = import_react34.useState(false);
51534
+ const [statusMessage, setStatusMessage] = import_react34.useState(null);
51535
+ const [isInstalled, setIsInstalled] = import_react34.useState(false);
51536
+ const installingRef = import_react34.useRef(false);
51537
+ import_react34.useEffect(() => {
51538
+ const init = async () => {
51539
+ if (!params.uuid) {
51540
+ setError("No UUID provided");
51541
+ setIsLoading(false);
51542
+ return;
51543
+ }
51544
+ try {
51545
+ await libraryService5.ensureGlobalTables();
51546
+ const fetched = await componentService4.fetchCommunity(params.uuid);
51547
+ if (fetched) {
51548
+ setComponent(fetched);
51549
+ const installed = await libraryService5.isEasyEDAInstalled(fetched.title);
51550
+ setIsInstalled(installed);
51551
+ } else {
51552
+ setError("Component not found");
51553
+ }
51554
+ } catch (err) {
51555
+ setError(err instanceof Error ? err.message : "Failed to fetch component");
51556
+ } finally {
51557
+ setIsLoading(false);
51558
+ }
51559
+ };
51560
+ init();
51561
+ }, [params.uuid]);
51562
+ use_input_default((input, key) => {
51563
+ if (isLoading || !component || isInstalling)
51564
+ return;
51565
+ const lowerInput = input.toLowerCase();
51566
+ if (lowerInput === "r") {
51567
+ if (installingRef.current)
51568
+ return;
51569
+ installingRef.current = true;
51570
+ setIsInstalling(true);
51571
+ setStatusMessage("Regenerating symbol and footprint...");
51572
+ libraryService5.install(params.uuid, { force: true }).then((result) => {
51573
+ setStatusMessage(`✓ Reinstalled: ${result.symbolRef}`);
51574
+ setIsInstalled(true);
51575
+ setTimeout(() => setStatusMessage(null), 3000);
51576
+ }).catch((err) => {
51577
+ setStatusMessage(`✗ Failed: ${err instanceof Error ? err.message : "Unknown error"}`);
51578
+ setTimeout(() => setStatusMessage(null), 3000);
51579
+ }).finally(() => {
51580
+ setIsInstalling(false);
51581
+ installingRef.current = false;
51582
+ });
51583
+ return;
51584
+ }
51585
+ if (key.return) {
51586
+ if (installingRef.current)
51587
+ return;
51588
+ installingRef.current = true;
51589
+ setIsInstalling(true);
51590
+ setStatusMessage("Installing component...");
51591
+ libraryService5.ensureGlobalTables().then(() => libraryService5.install(params.uuid, {})).then((result) => {
51592
+ if (result.symbolAction === "exists") {
51593
+ setStatusMessage(`⚡ Already installed (use R to reinstall)`);
51594
+ } else {
51595
+ setStatusMessage(`✓ Installed: ${result.symbolRef}`);
51596
+ setIsInstalled(true);
51597
+ }
51598
+ setTimeout(() => setStatusMessage(null), 3000);
51599
+ }).catch((err) => {
51600
+ setStatusMessage(`✗ Failed: ${err instanceof Error ? err.message : "Unknown error"}`);
51601
+ setTimeout(() => setStatusMessage(null), 3000);
51602
+ }).finally(() => {
51603
+ setIsInstalling(false);
51604
+ installingRef.current = false;
51605
+ });
51606
+ }
51607
+ });
51608
+ if (isLoading) {
51609
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
51610
+ flexDirection: "column",
51611
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51612
+ color: "yellow",
51613
+ children: [
51614
+ "⏳ Loading component ",
51615
+ params.uuid,
51616
+ "..."
51617
+ ]
51618
+ }, undefined, true, undefined, this)
51619
+ }, undefined, false, undefined, this);
51620
+ }
51621
+ if (error || !component) {
51622
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
51623
+ flexDirection: "column",
51624
+ children: [
51625
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51626
+ color: "red",
51627
+ children: [
51628
+ "✗ ",
51629
+ error || "Component not found"
51630
+ ]
51631
+ }, undefined, true, undefined, this),
51632
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51633
+ dimColor: true,
51634
+ children: "Press Esc to go back"
51635
+ }, undefined, false, undefined, this)
51636
+ ]
51637
+ }, undefined, true, undefined, this);
51638
+ }
51639
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
51640
+ flexDirection: "column",
51641
+ children: [
51642
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Box_default, {
51643
+ marginBottom: 1,
51644
+ children: /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51645
+ bold: true,
51646
+ children: [
51647
+ "Component: ",
51648
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51649
+ color: "cyan",
51650
+ children: component.title
51651
+ }, undefined, false, undefined, this),
51652
+ isInstalled && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(Text, {
51653
+ color: "green",
51654
+ children: " ✓ Installed"
51655
+ }, undefined, false, undefined, this)
51656
+ ]
51657
+ }, undefined, true, undefined, this)
51658
+ }, undefined, false, undefined, this),
51659
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(EasyEDADetailView, {
51660
+ component,
51661
+ terminalWidth,
51662
+ isInstalled,
51663
+ statusMessage
51664
+ }, undefined, false, undefined, this)
51665
+ ]
51666
+ }, undefined, true, undefined, this);
51667
+ }
51668
+
51669
+ // src/app/App.tsx
51670
+ var jsx_dev_runtime15 = __toESM(require_jsx_dev_runtime(), 1);
50848
51671
  function ScreenRouter() {
50849
51672
  const { screen } = useCurrentScreen();
50850
51673
  switch (screen) {
50851
51674
  case "search":
50852
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(SearchScreen, {}, undefined, false, undefined, this);
51675
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(SearchScreen, {}, undefined, false, undefined, this);
50853
51676
  case "info":
50854
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(InfoScreen, {}, undefined, false, undefined, this);
51677
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(InfoScreen, {}, undefined, false, undefined, this);
50855
51678
  case "install":
50856
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(InstallScreen, {}, undefined, false, undefined, this);
51679
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(InstallScreen, {}, undefined, false, undefined, this);
50857
51680
  case "installed":
50858
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(InstalledScreen, {}, undefined, false, undefined, this);
51681
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(InstalledScreen, {}, undefined, false, undefined, this);
50859
51682
  case "library":
50860
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(LibraryScreen, {}, undefined, false, undefined, this);
51683
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(LibraryScreen, {}, undefined, false, undefined, this);
50861
51684
  case "library-setup":
50862
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(LibrarySetupScreen, {}, undefined, false, undefined, this);
51685
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(LibrarySetupScreen, {}, undefined, false, undefined, this);
51686
+ case "easyeda-info":
51687
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(EasyEDAInfoScreen, {}, undefined, false, undefined, this);
50863
51688
  default:
50864
51689
  return null;
50865
51690
  }
@@ -50881,34 +51706,34 @@ function AppContent() {
50881
51706
  exit();
50882
51707
  }
50883
51708
  });
50884
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
51709
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Box_default, {
50885
51710
  flexDirection: "column",
50886
51711
  padding: 1,
50887
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(ScreenRouter, {}, undefined, false, undefined, this)
51712
+ children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(ScreenRouter, {}, undefined, false, undefined, this)
50888
51713
  }, undefined, false, undefined, this);
50889
51714
  }
50890
51715
  function App2({ initialScreen, initialParams }) {
50891
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(NavigationProvider, {
51716
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(NavigationProvider, {
50892
51717
  initialScreen,
50893
51718
  initialParams,
50894
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(AppStateProvider, {
50895
- children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(AppContent, {}, undefined, false, undefined, this)
51719
+ children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(AppStateProvider, {
51720
+ children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(AppContent, {}, undefined, false, undefined, this)
50896
51721
  }, undefined, false, undefined, this)
50897
51722
  }, undefined, false, undefined, this);
50898
51723
  }
50899
51724
  function renderApp(initialScreen, initialParams) {
50900
- render_default(/* @__PURE__ */ jsx_dev_runtime13.jsxDEV(App2, {
51725
+ render_default(/* @__PURE__ */ jsx_dev_runtime15.jsxDEV(App2, {
50901
51726
  initialScreen,
50902
51727
  initialParams
50903
51728
  }, undefined, false, undefined, this));
50904
51729
  }
50905
51730
 
50906
51731
  // src/commands/search.ts
50907
- var componentService4 = createComponentService();
51732
+ var componentService5 = createComponentService();
50908
51733
  async function searchCommand(query, options) {
50909
51734
  console.log(`Searching for "${query}"...`);
50910
51735
  try {
50911
- let results = await componentService4.search(query, options);
51736
+ let results = await componentService5.search(query, options);
50912
51737
  if (!options.basicOnly) {
50913
51738
  results = results.sort((a, b) => {
50914
51739
  if (a.libraryType === "basic" && b.libraryType !== "basic")
@@ -51439,13 +52264,13 @@ var L2 = () => {
51439
52264
  };
51440
52265
 
51441
52266
  // src/commands/info.ts
51442
- var componentService5 = createComponentService();
52267
+ var componentService6 = createComponentService();
51443
52268
  async function infoCommand(id, options) {
51444
52269
  if (options.json) {
51445
52270
  const spinner = L2();
51446
52271
  spinner.start(`Fetching component ${id}...`);
51447
52272
  try {
51448
- const details = await componentService5.getDetails(id);
52273
+ const details = await componentService6.getDetails(id);
51449
52274
  spinner.stop("Component found");
51450
52275
  console.log(JSON.stringify(details, null, 2));
51451
52276
  } catch (error) {
@@ -51459,15 +52284,23 @@ async function infoCommand(id, options) {
51459
52284
  }
51460
52285
 
51461
52286
  // src/commands/install.ts
51462
- var componentService6 = createComponentService();
51463
- var libraryService5 = createLibraryService();
52287
+ var componentService7 = createComponentService();
52288
+ var libraryService6 = createLibraryService();
52289
+ function isLcscId3(id) {
52290
+ return /^C\d+$/i.test(id);
52291
+ }
51464
52292
  async function installCommand(id, options) {
52293
+ if (id && !isLcscId3(id)) {
52294
+ v2.error(`"${id}" is not an LCSC part number (e.g., C2040).`);
52295
+ v2.info(`For EasyEDA community components, use: ${source_default.cyan(`jlc easyeda install ${id}`)}`);
52296
+ process.exit(1);
52297
+ }
51465
52298
  if (id && options.force) {
51466
52299
  const spinner2 = L2();
51467
52300
  spinner2.start(`Installing component ${id}...`);
51468
52301
  try {
51469
- await libraryService5.ensureGlobalTables();
51470
- const result = await libraryService5.install(id, {
52302
+ await libraryService6.ensureGlobalTables();
52303
+ const result = await libraryService6.install(id, {
51471
52304
  projectPath: options.projectPath,
51472
52305
  include3d: options.include3d,
51473
52306
  force: true
@@ -51493,7 +52326,7 @@ async function installCommand(id, options) {
51493
52326
  const spinner2 = L2();
51494
52327
  spinner2.start(`Fetching component ${id}...`);
51495
52328
  try {
51496
- const details = await componentService6.getDetails(id);
52329
+ const details = await componentService7.getDetails(id);
51497
52330
  spinner2.stop("Component found");
51498
52331
  renderApp("info", { componentId: id, component: details });
51499
52332
  } catch (error) {
@@ -51519,7 +52352,7 @@ async function installCommand(id, options) {
51519
52352
  const spinner = L2();
51520
52353
  spinner.start(`Searching for "${query}"...`);
51521
52354
  const searchOptions = { limit: 20 };
51522
- let results = await componentService6.search(query, searchOptions);
52355
+ let results = await componentService7.search(query, searchOptions);
51523
52356
  results = results.sort((a2, b2) => {
51524
52357
  if (a2.libraryType === "basic" && b2.libraryType !== "basic")
51525
52358
  return -1;
@@ -51536,15 +52369,15 @@ async function installCommand(id, options) {
51536
52369
  }
51537
52370
 
51538
52371
  // src/commands/library.ts
51539
- var libraryService6 = createLibraryService();
52372
+ var libraryService7 = createLibraryService();
51540
52373
  async function libraryCommand(options) {
51541
52374
  if (options.json) {
51542
52375
  const spinner = L2();
51543
52376
  spinner.start("Loading library status...");
51544
52377
  try {
51545
52378
  const [status, components] = await Promise.all([
51546
- libraryService6.getStatus(),
51547
- libraryService6.listInstalled({})
52379
+ libraryService7.getStatus(),
52380
+ libraryService7.listInstalled({})
51548
52381
  ]);
51549
52382
  spinner.stop(`Found ${components.length} installed components`);
51550
52383
  console.log(JSON.stringify({
@@ -51567,6 +52400,91 @@ async function libraryCommand(options) {
51567
52400
  renderApp("library", {});
51568
52401
  }
51569
52402
 
52403
+ // src/commands/easyeda.ts
52404
+ var componentService8 = createComponentService();
52405
+ var libraryService8 = createLibraryService();
52406
+ async function easyedaSearchCommand(query, options) {
52407
+ const port = options.port ?? 3847;
52408
+ console.log("Starting component browser...");
52409
+ startHttpServer({
52410
+ port,
52411
+ onReady: async (url) => {
52412
+ const searchUrl = query ? `${url}?q=${encodeURIComponent(query)}` : url;
52413
+ console.log(`Browser opened at ${searchUrl}`);
52414
+ await open_default(searchUrl);
52415
+ console.log("Press Ctrl+C to stop the server and exit");
52416
+ }
52417
+ });
52418
+ await new Promise((resolve) => {
52419
+ process.on("SIGINT", () => {
52420
+ console.log(`
52421
+ Shutting down server...`);
52422
+ stopHttpServer();
52423
+ resolve();
52424
+ process.exit(0);
52425
+ });
52426
+ });
52427
+ }
52428
+ async function easyedaInstallCommand(uuid, options) {
52429
+ if (uuid && options.force) {
52430
+ const spinner2 = L2();
52431
+ spinner2.start(`Installing EasyEDA component ${uuid}...`);
52432
+ try {
52433
+ await libraryService8.ensureGlobalTables();
52434
+ const result = await libraryService8.install(uuid, {
52435
+ projectPath: options.projectPath,
52436
+ include3d: options.include3d,
52437
+ force: true
52438
+ });
52439
+ spinner2.stop(source_default.green("✓ Component installed"));
52440
+ console.log();
52441
+ console.log(source_default.cyan("Symbol: "), result.symbolRef);
52442
+ console.log(source_default.cyan("Footprint: "), result.footprintRef);
52443
+ console.log(source_default.cyan("Action: "), result.symbolAction);
52444
+ if (result.files.model3d) {
52445
+ console.log(source_default.cyan("3D Model: "), result.files.model3d);
52446
+ }
52447
+ console.log();
52448
+ console.log(source_default.dim(`Library: ${result.files.symbolLibrary}`));
52449
+ } catch (error) {
52450
+ spinner2.stop(source_default.red("✗ Installation failed"));
52451
+ v2.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
52452
+ process.exit(1);
52453
+ }
52454
+ return;
52455
+ }
52456
+ if (uuid) {
52457
+ renderApp("easyeda-info", { uuid });
52458
+ return;
52459
+ }
52460
+ const query = await ue({
52461
+ message: "Search EasyEDA community library:",
52462
+ placeholder: "e.g., STM32F103, ESP32, Arduino Nano",
52463
+ validate: (value) => {
52464
+ if (!value)
52465
+ return "Please enter a search term";
52466
+ return;
52467
+ }
52468
+ });
52469
+ if (BD(query)) {
52470
+ ve("Installation cancelled");
52471
+ process.exit(0);
52472
+ }
52473
+ const spinner = L2();
52474
+ spinner.start(`Searching EasyEDA community for "${query}"...`);
52475
+ const searchOptions = {
52476
+ limit: 20,
52477
+ source: "easyeda-community"
52478
+ };
52479
+ const results = await componentService8.search(query, searchOptions);
52480
+ spinner.stop(`Found ${results.length} results`);
52481
+ if (results.length === 0) {
52482
+ v2.warn("No components found. Try a different search term.");
52483
+ return;
52484
+ }
52485
+ renderApp("search", { query, results });
52486
+ }
52487
+
51570
52488
  // src/index.ts
51571
52489
  var program2 = new Command;
51572
52490
  program2.name("jlc").description("JLC/EasyEDA component sourcing and KiCad library management").version("0.1.0");
@@ -51594,4 +52512,18 @@ program2.command("library").description("View JLC-MCP library status and install
51594
52512
  json: options.json
51595
52513
  });
51596
52514
  });
52515
+ var easyeda = program2.command("easyeda").description("EasyEDA community component browser");
52516
+ easyeda.command("search <query...>").description("Open browser-based component search").option("-p, --port <number>", "HTTP server port", "3847").action(async (queryParts, options) => {
52517
+ const query = queryParts.join(" ");
52518
+ await easyedaSearchCommand(query, {
52519
+ port: options.port ? parseInt(options.port, 10) : undefined
52520
+ });
52521
+ });
52522
+ easyeda.command("install [uuid]").description("Install EasyEDA community component to KiCad libraries").option("-p, --project <path>", "Install to project-local library").option("--with-3d", "Include 3D model").option("-f, --force", "Force reinstall (regenerate symbol and footprint)").action(async (uuid, options) => {
52523
+ await easyedaInstallCommand(uuid, {
52524
+ projectPath: options.project,
52525
+ include3d: options.with3d,
52526
+ force: options.force
52527
+ });
52528
+ });
51597
52529
  program2.parse();