@jlcpcb/mcp 0.2.0 → 0.3.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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __create = Object.create;
4
3
  var __getProtoOf = Object.getPrototypeOf;
5
4
  var __defProp = Object.defineProperty;
@@ -26,7 +25,6 @@ var __export = (target, all) => {
26
25
  set: (newValue) => all[name] = () => newValue
27
26
  });
28
27
  };
29
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
28
 
31
29
  // ../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js
32
30
  var require_code = __commonJS((exports) => {
@@ -11947,6 +11945,9 @@ var require_dist = __commonJS((exports, module) => {
11947
11945
  exports.default = formatsPlugin;
11948
11946
  });
11949
11947
 
11948
+ // src/index.ts
11949
+ import { createRequire } from "module";
11950
+
11950
11951
  // ../../node_modules/zod/v3/external.js
11951
11952
  var exports_external = {};
11952
11953
  __export(exports_external, {
@@ -21890,22 +21891,25 @@ class StdioServerTransport {
21890
21891
  }
21891
21892
  // ../core/dist/index.js
21892
21893
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
21893
- import { existsSync } from "fs";
21894
- import { join as join2 } from "path";
21895
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
21896
21894
  import { existsSync as existsSync2 } from "fs";
21897
21895
  import { join as join3 } from "path";
21898
- import { homedir, platform } from "os";
21896
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
21897
+ import { existsSync as existsSync3 } from "fs";
21898
+ import { join as join4 } from "path";
21899
+ import { homedir as homedir2, platform as platform2 } from "os";
21899
21900
  import { readdir, readFile, writeFile, mkdir, access, stat } from "fs/promises";
21900
21901
  import { join, dirname, extname, basename } from "path";
21902
+ import { existsSync } from "fs";
21903
+ import { homedir, platform } from "os";
21904
+ import { join as join2 } from "path";
21901
21905
  import { execSync } from "child_process";
21902
- import { existsSync as existsSync3 } from "fs";
21903
- import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
21904
- import { homedir as homedir2, platform as platform2 } from "os";
21905
- import { join as join4 } from "path";
21906
+ import { existsSync as existsSync4 } from "fs";
21907
+ import { readFile as readFile4, readdir as readdir2, unlink } from "fs/promises";
21908
+ import { homedir as homedir3, platform as platform3 } from "os";
21909
+ import { join as join5 } from "path";
21906
21910
  import { createServer } from "http";
21907
21911
  import { readFileSync } from "fs";
21908
- import { join as join5, dirname as dirname3 } from "path";
21912
+ import { join as join6, dirname as dirname3 } from "path";
21909
21913
  import { fileURLToPath } from "url";
21910
21914
  var __defProp2 = Object.defineProperty;
21911
21915
  var __export2 = (target, all) => {
@@ -21934,9 +21938,16 @@ function containsKeyword(text, keyword) {
21934
21938
  return false;
21935
21939
  }
21936
21940
  function getLibraryCategory(prefix, category, description) {
21937
- const normalizedPrefix = prefix.toUpperCase();
21938
- if (PREFIX_CATEGORY_MAP[normalizedPrefix]) {
21939
- return PREFIX_CATEGORY_MAP[normalizedPrefix];
21941
+ const cleanedPrefix = prefix.replace(/[^a-zA-Z0-9]/g, "").toUpperCase();
21942
+ if (cleanedPrefix.length >= 2) {
21943
+ const twoCharPrefix = cleanedPrefix.slice(0, 2);
21944
+ if (PREFIX_CATEGORY_MAP[twoCharPrefix]) {
21945
+ return PREFIX_CATEGORY_MAP[twoCharPrefix];
21946
+ }
21947
+ }
21948
+ const singleCharPrefix = cleanedPrefix.slice(0, 1);
21949
+ if (PREFIX_CATEGORY_MAP[singleCharPrefix]) {
21950
+ return PREFIX_CATEGORY_MAP[singleCharPrefix];
21940
21951
  }
21941
21952
  const searchText = [category || "", description || ""].join(" ");
21942
21953
  if (searchText.trim()) {
@@ -22000,6 +22011,7 @@ var init_category_router = __esm(() => {
22000
22011
  X: "Crystals",
22001
22012
  J: "Connectors",
22002
22013
  P: "Connectors",
22014
+ RJ: "Connectors",
22003
22015
  K: "Misc",
22004
22016
  F: "Misc"
22005
22017
  };
@@ -22245,6 +22257,48 @@ var init_category_router = __esm(() => {
22245
22257
  "ceramic resonator"
22246
22258
  ]
22247
22259
  },
22260
+ {
22261
+ category: "Connectors",
22262
+ keywords: [
22263
+ "connector",
22264
+ "header",
22265
+ "socket",
22266
+ "terminal",
22267
+ "terminal block",
22268
+ "jack",
22269
+ "plug",
22270
+ "receptacle",
22271
+ "usb-c",
22272
+ "usb type-c",
22273
+ "micro usb",
22274
+ "mini usb",
22275
+ "usb-a",
22276
+ "usb-b",
22277
+ "hdmi connector",
22278
+ "rj45",
22279
+ "rj11",
22280
+ "barrel jack",
22281
+ "dc jack",
22282
+ "audio jack",
22283
+ "jst",
22284
+ "jst-xh",
22285
+ "jst-ph",
22286
+ "molex",
22287
+ "dupont",
22288
+ "pin header",
22289
+ "female header",
22290
+ "fpc",
22291
+ "ffc",
22292
+ "sim card",
22293
+ "sd card",
22294
+ "microsd",
22295
+ "pogo pin",
22296
+ "spring contact",
22297
+ "test point",
22298
+ "ethernet connector",
22299
+ "modular connector"
22300
+ ]
22301
+ },
22248
22302
  {
22249
22303
  category: "Transistors",
22250
22304
  keywords: [
@@ -22345,46 +22399,6 @@ var init_category_router = __esm(() => {
22345
22399
  "chip resistor"
22346
22400
  ]
22347
22401
  },
22348
- {
22349
- category: "Connectors",
22350
- keywords: [
22351
- "connector",
22352
- "header",
22353
- "socket",
22354
- "terminal",
22355
- "terminal block",
22356
- "jack",
22357
- "plug",
22358
- "receptacle",
22359
- "usb-c",
22360
- "usb type-c",
22361
- "micro usb",
22362
- "mini usb",
22363
- "usb-a",
22364
- "usb-b",
22365
- "hdmi connector",
22366
- "rj45",
22367
- "rj11",
22368
- "barrel jack",
22369
- "dc jack",
22370
- "audio jack",
22371
- "jst",
22372
- "jst-xh",
22373
- "jst-ph",
22374
- "molex",
22375
- "dupont",
22376
- "pin header",
22377
- "female header",
22378
- "fpc",
22379
- "ffc",
22380
- "sim card",
22381
- "sd card",
22382
- "microsd",
22383
- "pogo pin",
22384
- "spring contact",
22385
- "test point"
22386
- ]
22387
- },
22388
22402
  {
22389
22403
  category: "ICs",
22390
22404
  keywords: [
@@ -22478,9 +22492,9 @@ function addLibraryToTable(tableContent, libraryName, libraryPath, type, descrip
22478
22492
  `;
22479
22493
  }
22480
22494
  async function ensureSymLibTable(projectDir, symbolLibPath, libraryName = DEFAULT_LIBRARY_NAME, description = DEFAULT_LIBRARY_DESCRIPTION) {
22481
- const tablePath = join2(projectDir, "sym-lib-table");
22495
+ const tablePath = join3(projectDir, "sym-lib-table");
22482
22496
  const relativePath = symbolLibPath.startsWith(projectDir) ? "${KIPRJMOD}" + symbolLibPath.slice(projectDir.length) : symbolLibPath;
22483
- if (!existsSync(tablePath)) {
22497
+ if (!existsSync2(tablePath)) {
22484
22498
  const content = generateSymLibTable(relativePath, libraryName, description);
22485
22499
  await writeFile2(tablePath, content, "utf-8");
22486
22500
  return { created: true, modified: false, path: tablePath };
@@ -22494,9 +22508,9 @@ async function ensureSymLibTable(projectDir, symbolLibPath, libraryName = DEFAUL
22494
22508
  return { created: false, modified: true, path: tablePath };
22495
22509
  }
22496
22510
  async function ensureFpLibTable(projectDir, footprintLibPath, libraryName = DEFAULT_LIBRARY_NAME, description = DEFAULT_LIBRARY_DESCRIPTION) {
22497
- const tablePath = join2(projectDir, "fp-lib-table");
22511
+ const tablePath = join3(projectDir, "fp-lib-table");
22498
22512
  const relativePath = footprintLibPath.startsWith(projectDir) ? "${KIPRJMOD}" + footprintLibPath.slice(projectDir.length) : footprintLibPath;
22499
- if (!existsSync(tablePath)) {
22513
+ if (!existsSync2(tablePath)) {
22500
22514
  const content = generateFpLibTable(relativePath, libraryName, description);
22501
22515
  await writeFile2(tablePath, content, "utf-8");
22502
22516
  return { created: true, modified: false, path: tablePath };
@@ -22520,37 +22534,37 @@ __export2(exports_global_lib_table, {
22520
22534
  ensureGlobalLibraryTables: () => ensureGlobalLibraryTables,
22521
22535
  ensureGlobalEasyEDALibrary: () => ensureGlobalEasyEDALibrary
22522
22536
  });
22523
- function detectKicadVersion() {
22524
- const home = homedir();
22525
- const baseDir = join3(home, "Documents", "KiCad");
22537
+ function detectKicadVersion2() {
22538
+ const home = homedir2();
22539
+ const baseDir = join4(home, "Documents", "KiCad");
22526
22540
  for (const version2 of KICAD_VERSIONS2) {
22527
- if (existsSync2(join3(baseDir, version2))) {
22541
+ if (existsSync3(join4(baseDir, version2))) {
22528
22542
  return version2;
22529
22543
  }
22530
22544
  }
22531
22545
  return "9.0";
22532
22546
  }
22533
- function getKicadConfigDir(version2) {
22534
- const home = homedir();
22535
- const plat = platform();
22547
+ function getKicadConfigDir2(version2) {
22548
+ const home = homedir2();
22549
+ const plat = platform2();
22536
22550
  if (plat === "darwin") {
22537
- return join3(home, "Library", "Preferences", "kicad", version2);
22551
+ return join4(home, "Library", "Preferences", "kicad", version2);
22538
22552
  } else if (plat === "win32") {
22539
- return join3(process.env.APPDATA || join3(home, "AppData", "Roaming"), "kicad", version2);
22553
+ return join4(process.env.APPDATA || join4(home, "AppData", "Roaming"), "kicad", version2);
22540
22554
  } else {
22541
- return join3(home, ".config", "kicad", version2);
22555
+ return join4(home, ".config", "kicad", version2);
22542
22556
  }
22543
22557
  }
22544
22558
  function get3rdPartyBaseDir(version2) {
22545
- const home = homedir();
22546
- const plat = platform();
22559
+ const home = homedir2();
22560
+ const plat = platform2();
22547
22561
  if (plat === "linux") {
22548
- return join3(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE);
22562
+ return join4(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE);
22549
22563
  }
22550
- return join3(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE);
22564
+ return join4(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE);
22551
22565
  }
22552
22566
  function getSymbolLibPath(category, version2) {
22553
- return join3(get3rdPartyBaseDir(version2), "symbols", getLibraryFilename(category));
22567
+ return join4(get3rdPartyBaseDir(version2), "symbols", getLibraryFilename(category));
22554
22568
  }
22555
22569
  function getSymbolLibUri(category) {
22556
22570
  return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${getLibraryFilename(category)}`;
@@ -22591,11 +22605,11 @@ function generateFpLibTable2() {
22591
22605
  `;
22592
22606
  }
22593
22607
  async function ensureGlobalSymLibTable(version2) {
22594
- const configDir = getKicadConfigDir(version2);
22595
- const tablePath = join3(configDir, "sym-lib-table");
22608
+ const configDir = getKicadConfigDir2(version2);
22609
+ const tablePath = join4(configDir, "sym-lib-table");
22596
22610
  const categories = getAllCategories();
22597
22611
  await mkdir2(configDir, { recursive: true });
22598
- if (!existsSync2(tablePath)) {
22612
+ if (!existsSync3(tablePath)) {
22599
22613
  const content2 = generateSymLibTable2();
22600
22614
  await writeFile3(tablePath, content2, "utf-8");
22601
22615
  return {
@@ -22635,11 +22649,11 @@ async function ensureGlobalSymLibTable(version2) {
22635
22649
  };
22636
22650
  }
22637
22651
  async function ensureGlobalFpLibTable(version2) {
22638
- const configDir = getKicadConfigDir(version2);
22639
- const tablePath = join3(configDir, "fp-lib-table");
22652
+ const configDir = getKicadConfigDir2(version2);
22653
+ const tablePath = join4(configDir, "fp-lib-table");
22640
22654
  const name = LIBRARY_PREFIX2;
22641
22655
  await mkdir2(configDir, { recursive: true });
22642
- if (!existsSync2(tablePath)) {
22656
+ if (!existsSync3(tablePath)) {
22643
22657
  const content2 = generateFpLibTable2();
22644
22658
  await writeFile3(tablePath, content2, "utf-8");
22645
22659
  return {
@@ -22673,13 +22687,13 @@ async function ensureGlobalFpLibTable(version2) {
22673
22687
  }
22674
22688
  async function ensureLibraryStubs(version2) {
22675
22689
  const baseDir = get3rdPartyBaseDir(version2);
22676
- const symbolsDir = join3(baseDir, "symbols");
22677
- const footprintsDir = join3(baseDir, "footprints", getFootprintDirName());
22678
- const models3dDir = join3(baseDir, "3dmodels", get3DModelsDirName());
22690
+ const symbolsDir = join4(baseDir, "symbols");
22691
+ const footprintsDir = join4(baseDir, "footprints", getFootprintDirName());
22692
+ const models3dDir = join4(baseDir, "3dmodels", get3DModelsDirName());
22679
22693
  const directoriesCreated = [];
22680
22694
  const symbolsCreated = [];
22681
22695
  for (const dir of [symbolsDir, footprintsDir, models3dDir]) {
22682
- if (!existsSync2(dir)) {
22696
+ if (!existsSync3(dir)) {
22683
22697
  await mkdir2(dir, { recursive: true });
22684
22698
  directoriesCreated.push(dir);
22685
22699
  }
@@ -22688,7 +22702,7 @@ async function ensureLibraryStubs(version2) {
22688
22702
  const emptyContent = generateEmptySymbolLibrary();
22689
22703
  for (const category of categories) {
22690
22704
  const filePath = getSymbolLibPath(category, version2);
22691
- if (!existsSync2(filePath)) {
22705
+ if (!existsSync3(filePath)) {
22692
22706
  await writeFile3(filePath, emptyContent, "utf-8");
22693
22707
  symbolsCreated.push(filePath);
22694
22708
  }
@@ -22702,11 +22716,11 @@ function getEasyEDAFootprintLibUri() {
22702
22716
  return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/footprints/${EASYEDA_FOOTPRINT_LIBRARY_NAME}`;
22703
22717
  }
22704
22718
  async function ensureGlobalEasyEDALibrary() {
22705
- const version2 = detectKicadVersion();
22706
- const configDir = getKicadConfigDir(version2);
22719
+ const version2 = detectKicadVersion2();
22720
+ const configDir = getKicadConfigDir2(version2);
22707
22721
  await mkdir2(configDir, { recursive: true });
22708
- const symTablePath = join3(configDir, "sym-lib-table");
22709
- if (existsSync2(symTablePath)) {
22722
+ const symTablePath = join4(configDir, "sym-lib-table");
22723
+ if (existsSync3(symTablePath)) {
22710
22724
  let content = await readFile3(symTablePath, "utf-8");
22711
22725
  if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
22712
22726
  const uri = getEasyEDASymbolLibUri();
@@ -22722,8 +22736,8 @@ async function ensureGlobalEasyEDALibrary() {
22722
22736
  `;
22723
22737
  await writeFile3(symTablePath, content, "utf-8");
22724
22738
  }
22725
- const fpTablePath = join3(configDir, "fp-lib-table");
22726
- if (existsSync2(fpTablePath)) {
22739
+ const fpTablePath = join4(configDir, "fp-lib-table");
22740
+ if (existsSync3(fpTablePath)) {
22727
22741
  let content = await readFile3(fpTablePath, "utf-8");
22728
22742
  if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
22729
22743
  const uri = getEasyEDAFootprintLibUri();
@@ -22742,7 +22756,7 @@ async function ensureGlobalEasyEDALibrary() {
22742
22756
  }
22743
22757
  async function ensureGlobalLibraryTables() {
22744
22758
  const errors22 = [];
22745
- const version2 = detectKicadVersion();
22759
+ const version2 = detectKicadVersion2();
22746
22760
  let symLibTable = {
22747
22761
  path: "",
22748
22762
  created: false,
@@ -26770,6 +26784,9 @@ var coerce2 = {
26770
26784
  };
26771
26785
  var NEVER3 = INVALID2;
26772
26786
  var LCSCPartNumberSchema = exports_external2.string().regex(/^C\d+$/, "Invalid LCSC part number format (expected C followed by digits)");
26787
+ var EasyEDAUuidSchema = exports_external2.string().regex(/^[a-f0-9]{32}$/i, "Invalid EasyEDA UUID (expected 32-character hex string)").transform((s) => s.toLowerCase());
26788
+ var ComponentIdSchema = exports_external2.union([LCSCPartNumberSchema, EasyEDAUuidSchema]);
26789
+ var SafePathSchema = exports_external2.string().min(1, "Path cannot be empty").refine((p) => !p.includes(".."), "Path traversal (..) not allowed").refine((p) => !p.includes("\x00"), "Null bytes not allowed in path");
26773
26790
  var PackageSchema = exports_external2.string().min(1, "Package cannot be empty");
26774
26791
  var PriceTierSchema = exports_external2.object({
26775
26792
  quantity: exports_external2.number().positive(),
@@ -26845,6 +26862,9 @@ var KiCadPadShapeSchema = exports_external2.enum([
26845
26862
  "roundrect",
26846
26863
  "custom"
26847
26864
  ]);
26865
+ function isLcscId(id) {
26866
+ return LCSCPartNumberSchema.safeParse(id).success;
26867
+ }
26848
26868
  async function ensureDir(path) {
26849
26869
  await mkdir(path, { recursive: true });
26850
26870
  }
@@ -26926,7 +26946,58 @@ var logger = new Logger({ prefix: "ai-eda" });
26926
26946
  function createLogger(packageName) {
26927
26947
  return logger.child(packageName);
26928
26948
  }
26949
+ var KICAD_LAYERS = {
26950
+ F_CU: "F.Cu",
26951
+ B_CU: "B.Cu",
26952
+ IN1_CU: "In1.Cu",
26953
+ IN2_CU: "In2.Cu",
26954
+ IN3_CU: "In3.Cu",
26955
+ IN4_CU: "In4.Cu",
26956
+ F_ADHES: "F.Adhes",
26957
+ F_PASTE: "F.Paste",
26958
+ F_SILKS: "F.SilkS",
26959
+ F_MASK: "F.Mask",
26960
+ F_CRTYD: "F.CrtYd",
26961
+ F_FAB: "F.Fab",
26962
+ B_ADHES: "B.Adhes",
26963
+ B_PASTE: "B.Paste",
26964
+ B_SILKS: "B.SilkS",
26965
+ B_MASK: "B.Mask",
26966
+ B_CRTYD: "B.CrtYd",
26967
+ B_FAB: "B.Fab",
26968
+ EDGE_CUTS: "Edge.Cuts",
26969
+ MARGIN: "Margin",
26970
+ DWGS_USER: "Dwgs.User",
26971
+ CMTS_USER: "Cmts.User",
26972
+ ECO1_USER: "Eco1.User",
26973
+ ECO2_USER: "Eco2.User"
26974
+ };
26975
+ var KICAD_DEFAULTS = {
26976
+ TEXT_SIZE: 1.27,
26977
+ TEXT_THICKNESS: 0.15,
26978
+ WIRE_WIDTH: 0.25,
26979
+ GRID_SCHEMATIC: 2.54,
26980
+ GRID_PCB: 0.25,
26981
+ PIN_LENGTH: 1.27,
26982
+ PIN_NAME_OFFSET: 0
26983
+ };
26984
+ var KICAD_SYMBOL_VERSION = "20241209";
26985
+ var KICAD_FOOTPRINT_VERSION = "20241209";
26986
+ var KICAD_VERSIONS = ["9.0", "8.0"];
26987
+ function detectKicadVersion() {
26988
+ const home = homedir();
26989
+ const baseDir = join2(home, "Documents", "KiCad");
26990
+ for (const version2 of KICAD_VERSIONS) {
26991
+ if (existsSync(join2(baseDir, version2))) {
26992
+ return version2;
26993
+ }
26994
+ }
26995
+ return "9.0";
26996
+ }
26929
26997
  var logger3 = createLogger("jlc-api");
26998
+ function stripTemperaturePrefix(description) {
26999
+ return description.replace(/^-?\d+℃~[+-]?\d+℃\s*/, "");
27000
+ }
26930
27001
  var JLCPCB_SEARCH_API = "https://jlcpcb.com/api/overseas-pcb-order/v1/shoppingCart/smtGood/selectSmtComponentList/v2";
26931
27002
 
26932
27003
  class JLCClient {
@@ -26972,7 +27043,7 @@ class JLCClient {
26972
27043
  package: c.componentSpecificationEn || "",
26973
27044
  price: c.componentPrices?.[0]?.productPrice,
26974
27045
  stock: c.stockCount,
26975
- description: c.describe,
27046
+ description: stripTemperaturePrefix(c.describe),
26976
27047
  productUrl: c.lcscGoodsUrl,
26977
27048
  datasheetPdf: c.dataManualUrl,
26978
27049
  category: c.componentTypeEn,
@@ -27212,6 +27283,25 @@ function parseSymbolPath(data) {
27212
27283
  return null;
27213
27284
  }
27214
27285
  }
27286
+ function parseSymbolText(data) {
27287
+ try {
27288
+ const f = data.split("~");
27289
+ const isPinPart = f[f.length - 1] === "pinpart" || f.some((field) => field === "pinpart");
27290
+ return {
27291
+ x: safeParseFloat(f[2]),
27292
+ y: safeParseFloat(f[3]),
27293
+ rotation: safeParseFloat(f[4]),
27294
+ color: f[5] || "#000000",
27295
+ fontSize: safeParseFloat(f[7], 7),
27296
+ textType: f[11] || "",
27297
+ text: f[12] || "",
27298
+ id: f[15] || f[13] || "",
27299
+ isPinPart
27300
+ };
27301
+ } catch {
27302
+ return null;
27303
+ }
27304
+ }
27215
27305
  function parsePad(data) {
27216
27306
  try {
27217
27307
  const f = data.split("~");
@@ -27379,6 +27469,7 @@ function parseSymbolShapes(shapes) {
27379
27469
  const polylines = [];
27380
27470
  const polygons = [];
27381
27471
  const paths = [];
27472
+ const texts = [];
27382
27473
  for (const line of shapes) {
27383
27474
  if (typeof line !== "string")
27384
27475
  continue;
@@ -27432,10 +27523,15 @@ function parseSymbolShapes(shapes) {
27432
27523
  paths.push(path);
27433
27524
  break;
27434
27525
  }
27435
- case "T":
27526
+ case "T": {
27527
+ const text = parseSymbolText(line);
27528
+ if (text && text.text.trim())
27529
+ texts.push(text);
27436
27530
  break;
27531
+ }
27437
27532
  }
27438
27533
  }
27534
+ associatePinNamesFromTexts(pins, texts);
27439
27535
  return {
27440
27536
  pins,
27441
27537
  rectangles,
@@ -27444,8 +27540,29 @@ function parseSymbolShapes(shapes) {
27444
27540
  arcs,
27445
27541
  polylines,
27446
27542
  polygons,
27447
- paths
27448
- };
27543
+ paths,
27544
+ texts
27545
+ };
27546
+ }
27547
+ function associatePinNamesFromTexts(pins, texts) {
27548
+ const pinpartTexts = texts.filter((t) => t.isPinPart && t.text.trim());
27549
+ for (const text of pinpartTexts) {
27550
+ const textContent = text.text.trim();
27551
+ const jMatch = textContent.match(/^J(\d+)(?:\/(.+))?$/i);
27552
+ if (jMatch) {
27553
+ const pinNum = jMatch[1];
27554
+ const pinFunction = jMatch[2] || textContent;
27555
+ const pin = pins.find((p) => p.number === pinNum);
27556
+ if (pin && (!pin.name || pin.name === pinNum)) {
27557
+ pin.name = pinFunction || textContent;
27558
+ }
27559
+ continue;
27560
+ }
27561
+ const numMatch = textContent.match(/^(\d+)$/);
27562
+ if (numMatch) {
27563
+ continue;
27564
+ }
27565
+ }
27449
27566
  }
27450
27567
  function parseFootprintShapes(shapes) {
27451
27568
  const pads = [];
@@ -27633,6 +27750,7 @@ class EasyEDAClient {
27633
27750
  polylines: symbolData.polylines,
27634
27751
  polygons: symbolData.polygons,
27635
27752
  paths: symbolData.paths,
27753
+ texts: symbolData.texts,
27636
27754
  origin: symbolOrigin
27637
27755
  },
27638
27756
  footprint: {
@@ -27806,6 +27924,7 @@ class EasyEDACommunityClient {
27806
27924
  polylines: symbolData.polylines,
27807
27925
  polygons: symbolData.polygons,
27808
27926
  paths: symbolData.paths,
27927
+ texts: symbolData.texts,
27809
27928
  origin: symbolOrigin,
27810
27929
  head: dataStr.head || {}
27811
27930
  },
@@ -27832,14 +27951,14 @@ class EasyEDACommunityClient {
27832
27951
  }
27833
27952
  }
27834
27953
  var easyedaCommunityClient = new EasyEDACommunityClient;
27835
- function isLcscId(id) {
27954
+ function isLcscId2(id) {
27836
27955
  return /^C\d+$/.test(id);
27837
27956
  }
27838
27957
  function createComponentService() {
27839
27958
  return {
27840
27959
  async search(query, options = {}) {
27841
27960
  const { source = "lcsc", limit = 20, inStock, basicOnly } = options;
27842
- if (source === "easyeda-community") {
27961
+ if (source === "community" || source === "easyeda-community") {
27843
27962
  const results = await easyedaCommunityClient.search({
27844
27963
  query,
27845
27964
  limit
@@ -27856,7 +27975,7 @@ function createComponentService() {
27856
27975
  return jlcClient.search(query, { limit, inStock, basicOnly });
27857
27976
  },
27858
27977
  async fetch(id) {
27859
- if (isLcscId(id)) {
27978
+ if (isLcscId2(id)) {
27860
27979
  return easyedaClient.getComponentData(id);
27861
27980
  }
27862
27981
  return null;
@@ -27884,43 +28003,6 @@ function createComponentService() {
27884
28003
  }
27885
28004
  };
27886
28005
  }
27887
- var KICAD_LAYERS = {
27888
- F_CU: "F.Cu",
27889
- B_CU: "B.Cu",
27890
- IN1_CU: "In1.Cu",
27891
- IN2_CU: "In2.Cu",
27892
- IN3_CU: "In3.Cu",
27893
- IN4_CU: "In4.Cu",
27894
- F_ADHES: "F.Adhes",
27895
- F_PASTE: "F.Paste",
27896
- F_SILKS: "F.SilkS",
27897
- F_MASK: "F.Mask",
27898
- F_CRTYD: "F.CrtYd",
27899
- F_FAB: "F.Fab",
27900
- B_ADHES: "B.Adhes",
27901
- B_PASTE: "B.Paste",
27902
- B_SILKS: "B.SilkS",
27903
- B_MASK: "B.Mask",
27904
- B_CRTYD: "B.CrtYd",
27905
- B_FAB: "B.Fab",
27906
- EDGE_CUTS: "Edge.Cuts",
27907
- MARGIN: "Margin",
27908
- DWGS_USER: "Dwgs.User",
27909
- CMTS_USER: "Cmts.User",
27910
- ECO1_USER: "Eco1.User",
27911
- ECO2_USER: "Eco2.User"
27912
- };
27913
- var KICAD_DEFAULTS = {
27914
- TEXT_SIZE: 1.27,
27915
- TEXT_THICKNESS: 0.15,
27916
- WIRE_WIDTH: 0.25,
27917
- GRID_SCHEMATIC: 2.54,
27918
- GRID_PCB: 0.25,
27919
- PIN_LENGTH: 1.27,
27920
- PIN_NAME_OFFSET: 0
27921
- };
27922
- var KICAD_SYMBOL_VERSION = "20241209";
27923
- var KICAD_FOOTPRINT_VERSION = "20241209";
27924
28006
  var PATTERNS = {
27925
28007
  resistor: /(\d+(?:\.\d+)?)\s*([kKmMgGrR]?)\s*(?:ohm|Ohm|OHM|Ω|R)?/i,
27926
28008
  capacitor: /(\d+(?:\.\d+)?)\s*([pnuμmM]?)[Ff]?\s*(?:[\/\s]*(\d+)\s*[Vv])?/i,
@@ -28330,6 +28412,29 @@ function parseSvgArcPath(path) {
28330
28412
  return null;
28331
28413
  }
28332
28414
  }
28415
+ function interpolateArc(params, segmentsPerQuarter = 4) {
28416
+ const center = svgArcToCenter(params);
28417
+ if (!center) {
28418
+ return [{ x: params.x2, y: params.y2 }];
28419
+ }
28420
+ const { cx, cy, rx, ry, startAngle, deltaAngle } = center;
28421
+ const phiRad = params.phi * Math.PI / 180;
28422
+ const cosPhi = Math.cos(phiRad);
28423
+ const sinPhi = Math.sin(phiRad);
28424
+ const arcDegrees = Math.abs(deltaAngle) * (180 / Math.PI);
28425
+ const numSegments = Math.max(2, Math.ceil(arcDegrees / 90 * segmentsPerQuarter));
28426
+ const points = [];
28427
+ for (let i = 1;i <= numSegments; i++) {
28428
+ const t = i / numSegments;
28429
+ const angle = startAngle + deltaAngle * t;
28430
+ const px = rx * Math.cos(angle);
28431
+ const py = ry * Math.sin(angle);
28432
+ const x = cosPhi * px - sinPhi * py + cx;
28433
+ const y = sinPhi * px + cosPhi * py + cy;
28434
+ points.push({ x, y });
28435
+ }
28436
+ return points;
28437
+ }
28333
28438
  var CATEGORY_TO_PREFIX = {
28334
28439
  Resistors: "R",
28335
28440
  Capacitors: "C",
@@ -28392,7 +28497,12 @@ class SymbolConverter {
28392
28497
  return output;
28393
28498
  }
28394
28499
  hasRenderableShapes(symbol) {
28395
- return symbol.rectangles.length > 0 || symbol.circles.length > 0 || symbol.ellipses.length > 0 || symbol.polylines.length > 0 || symbol.polygons.length > 0 || symbol.arcs.length > 0 || symbol.paths.length > 0;
28500
+ const pinNamePattern = /^J\d+(?:\/|$)/i;
28501
+ const hasDecorativeText = (symbol.texts || []).some((t) => {
28502
+ const text = t.text.trim();
28503
+ return text && !pinNamePattern.test(text);
28504
+ });
28505
+ return symbol.rectangles.length > 0 || symbol.circles.length > 0 || symbol.ellipses.length > 0 || symbol.polylines.length > 0 || symbol.polygons.length > 0 || symbol.arcs.length > 0 || symbol.paths.length > 0 || hasDecorativeText;
28396
28506
  }
28397
28507
  generateFromShapes(component, name) {
28398
28508
  const { info, symbol } = component;
@@ -28426,6 +28536,18 @@ class SymbolConverter {
28426
28536
  if (pathOutput)
28427
28537
  output += pathOutput;
28428
28538
  }
28539
+ const pinNamePattern = /^J\d+(?:\/|$)/i;
28540
+ const decorativeTexts = (symbol.texts || []).filter((t) => {
28541
+ const text = t.text.trim();
28542
+ if (!text)
28543
+ return false;
28544
+ return !pinNamePattern.test(text);
28545
+ });
28546
+ for (const text of decorativeTexts) {
28547
+ const textOutput = this.convertText(text, origin);
28548
+ if (textOutput)
28549
+ output += textOutput;
28550
+ }
28429
28551
  output += ` )
28430
28552
  `;
28431
28553
  output += ` (symbol "${name}_1_1"
@@ -28610,6 +28732,24 @@ class SymbolConverter {
28610
28732
  `;
28611
28733
  return output;
28612
28734
  }
28735
+ convertText(text, origin) {
28736
+ if (!text.text || text.text.trim() === "")
28737
+ return null;
28738
+ const x = this.convertX(text.x, origin.x);
28739
+ const y = this.convertY(text.y, origin.y);
28740
+ const fontSize = roundTo(Math.max(text.fontSize * EE_TO_MM * 0.8, 0.5), 3);
28741
+ const rotation = (360 - text.rotation) % 360;
28742
+ const content = text.text.replace(/"/g, "'").replace(/\\/g, "");
28743
+ return ` (text "${content}"
28744
+ (at ${x} ${y} ${rotation})
28745
+ (effects
28746
+ (font
28747
+ (size ${fontSize} ${fontSize})
28748
+ )
28749
+ )
28750
+ )
28751
+ `;
28752
+ }
28613
28753
  parseSvgPath(pathStr, origin) {
28614
28754
  const points = [];
28615
28755
  let firstPoint = null;
@@ -29815,7 +29955,7 @@ class FootprintConverter {
29815
29955
  return this.generatePad({ ...pad, shape: "RECT", points: "" }, origin);
29816
29956
  }
29817
29957
  let holeRadius = pad.holeRadius;
29818
- if (holeRadius === 0 && pad.isPlated && pad.shape === "POLYGON") {
29958
+ if (holeRadius === 0 && pad.layerId === 11 && pad.shape === "POLYGON") {
29819
29959
  holeRadius = this.calculateDrillRadiusFromPolygon(points, pad.centerX, pad.centerY);
29820
29960
  }
29821
29961
  const isSmd = holeRadius === 0;
@@ -30157,12 +30297,26 @@ ${justify ? ` (justify ${justify})
30157
30297
  break;
30158
30298
  case "A":
30159
30299
  if (args.length >= 7) {
30300
+ const arcParams = {
30301
+ x1: currentX,
30302
+ y1: currentY,
30303
+ rx: args[0],
30304
+ ry: args[1],
30305
+ phi: args[2],
30306
+ largeArc: args[3] === 1,
30307
+ sweep: args[4] === 1,
30308
+ x2: args[5],
30309
+ y2: args[6]
30310
+ };
30311
+ const arcPoints = interpolateArc(arcParams, 4);
30312
+ for (const pt of arcPoints) {
30313
+ points.push({
30314
+ x: convertX(pt.x, origin.x),
30315
+ y: convertY(pt.y, origin.y)
30316
+ });
30317
+ }
30160
30318
  currentX = args[5];
30161
30319
  currentY = args[6];
30162
- points.push({
30163
- x: convertX(currentX, origin.x),
30164
- y: convertY(currentY, origin.y)
30165
- });
30166
30320
  }
30167
30321
  break;
30168
30322
  case "Z":
@@ -30336,7 +30490,6 @@ init_category_router();
30336
30490
  var FOOTPRINT_LIBRARY_NAME = getFootprintDirName();
30337
30491
  var MODELS_3D_NAME = get3DModelsDirName();
30338
30492
  var LIBRARY_NAMESPACE2 = "jlc_mcp";
30339
- var KICAD_VERSIONS3 = ["9.0", "8.0"];
30340
30493
  var EASYEDA_LIBRARY_NAME2 = "EasyEDA";
30341
30494
  var EASYEDA_SYMBOL_LIBRARY_NAME2 = "EasyEDA.kicad_sym";
30342
30495
  var EASYEDA_FOOTPRINT_LIBRARY_NAME2 = "EasyEDA.pretty";
@@ -30345,58 +30498,48 @@ var EASYEDA_LOCAL_LIBRARY_NAME = "EasyEDA-local";
30345
30498
  var EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME = "EasyEDA-local.kicad_sym";
30346
30499
  var EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME = "EasyEDA-local.pretty";
30347
30500
  var EASYEDA_LOCAL_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library (Project-local)";
30348
- function detectKicadVersion2() {
30349
- const home = homedir2();
30350
- const baseDir = join4(home, "Documents", "KiCad");
30351
- for (const version2 of KICAD_VERSIONS3) {
30352
- if (existsSync3(join4(baseDir, version2))) {
30353
- return version2;
30354
- }
30355
- }
30356
- return "9.0";
30357
- }
30358
30501
  function getGlobalLibraryPaths() {
30359
- const home = homedir2();
30360
- const version2 = detectKicadVersion2();
30361
- const plat = platform2();
30502
+ const home = homedir3();
30503
+ const version2 = detectKicadVersion();
30504
+ const plat = platform3();
30362
30505
  let base;
30363
30506
  if (plat === "linux") {
30364
- base = join4(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE2);
30507
+ base = join5(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE2);
30365
30508
  } else {
30366
- base = join4(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE2);
30509
+ base = join5(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE2);
30367
30510
  }
30368
30511
  return {
30369
30512
  base,
30370
- symbolsDir: join4(base, "symbols"),
30371
- footprintsDir: join4(base, "footprints"),
30372
- models3dDir: join4(base, "3dmodels"),
30373
- footprintDir: join4(base, "footprints", FOOTPRINT_LIBRARY_NAME),
30374
- models3dFullDir: join4(base, "3dmodels", MODELS_3D_NAME)
30513
+ symbolsDir: join5(base, "symbols"),
30514
+ footprintsDir: join5(base, "footprints"),
30515
+ models3dDir: join5(base, "3dmodels"),
30516
+ footprintDir: join5(base, "footprints", FOOTPRINT_LIBRARY_NAME),
30517
+ models3dFullDir: join5(base, "3dmodels", MODELS_3D_NAME)
30375
30518
  };
30376
30519
  }
30377
30520
  function getProjectLibraryPaths(projectPath) {
30378
- const librariesDir = join4(projectPath, "libraries");
30521
+ const librariesDir = join5(projectPath, "libraries");
30379
30522
  return {
30380
30523
  base: librariesDir,
30381
- symbolsDir: join4(librariesDir, "symbols"),
30382
- footprintsDir: join4(librariesDir, "footprints"),
30383
- models3dDir: join4(librariesDir, "3dmodels"),
30384
- footprintDir: join4(librariesDir, "footprints", FOOTPRINT_LIBRARY_NAME),
30385
- models3dFullDir: join4(librariesDir, "3dmodels", MODELS_3D_NAME)
30524
+ symbolsDir: join5(librariesDir, "symbols"),
30525
+ footprintsDir: join5(librariesDir, "footprints"),
30526
+ models3dDir: join5(librariesDir, "3dmodels"),
30527
+ footprintDir: join5(librariesDir, "footprints", FOOTPRINT_LIBRARY_NAME),
30528
+ models3dFullDir: join5(librariesDir, "3dmodels", MODELS_3D_NAME)
30386
30529
  };
30387
30530
  }
30388
- function isLcscId2(id) {
30531
+ function isLcscId3(id) {
30389
30532
  return /^C\d+$/.test(id);
30390
30533
  }
30391
- function getKicadConfigDir2(version2) {
30392
- const home = homedir2();
30393
- const plat = platform2();
30534
+ function getKicadConfigDir3(version2) {
30535
+ const home = homedir3();
30536
+ const plat = platform3();
30394
30537
  if (plat === "darwin") {
30395
- return join4(home, "Library", "Preferences", "kicad", version2);
30538
+ return join5(home, "Library", "Preferences", "kicad", version2);
30396
30539
  } else if (plat === "win32") {
30397
- return join4(process.env.APPDATA || join4(home, "AppData", "Roaming"), "kicad", version2);
30540
+ return join5(process.env.APPDATA || join5(home, "AppData", "Roaming"), "kicad", version2);
30398
30541
  } else {
30399
- return join4(home, ".config", "kicad", version2);
30542
+ return join5(home, ".config", "kicad", version2);
30400
30543
  }
30401
30544
  }
30402
30545
  async function parseSymbolLibrary(filePath, libraryName, category, models3dDir) {
@@ -30422,8 +30565,8 @@ async function parseSymbolLibrary(filePath, libraryName, category, models3dDir)
30422
30565
  const name = nameParts.length > 1 ? nameParts[1] : fullSymbolRef;
30423
30566
  const fpMatch = symbolBlock.match(/\(property\s+"Footprint"\s+"([^"]+)"/);
30424
30567
  const footprintRef = fpMatch ? fpMatch[1] : "";
30425
- const model3dPath = join4(models3dDir, `${name}.step`);
30426
- const has3dModel = existsSync3(model3dPath);
30568
+ const model3dPath = join5(models3dDir, `${name}.step`);
30569
+ const has3dModel = existsSync4(model3dPath);
30427
30570
  if (lcscId) {
30428
30571
  components.push({
30429
30572
  lcscId,
@@ -30439,6 +30582,42 @@ async function parseSymbolLibrary(filePath, libraryName, category, models3dDir)
30439
30582
  } catch {}
30440
30583
  return components;
30441
30584
  }
30585
+ function removeSymbolFromLibrary(content, symbolName) {
30586
+ const lines = content.split(`
30587
+ `);
30588
+ const result = [];
30589
+ let depth = 0;
30590
+ let inTargetSymbol = false;
30591
+ let symbolStartDepth = 0;
30592
+ for (const line of lines) {
30593
+ const openCount = (line.match(/\(/g) || []).length;
30594
+ const closeCount = (line.match(/\)/g) || []).length;
30595
+ const symbolMatch = line.match(/^\s*\(symbol\s+"([^"]+)"/);
30596
+ if (symbolMatch && !inTargetSymbol) {
30597
+ const fullRef = symbolMatch[1];
30598
+ const nameOnly = fullRef.includes(":") ? fullRef.split(":")[1] : fullRef;
30599
+ if (nameOnly === symbolName || nameOnly.startsWith(`${symbolName}_`)) {
30600
+ inTargetSymbol = true;
30601
+ symbolStartDepth = depth;
30602
+ depth += openCount - closeCount;
30603
+ continue;
30604
+ }
30605
+ }
30606
+ if (inTargetSymbol) {
30607
+ depth += openCount - closeCount;
30608
+ if (depth <= symbolStartDepth) {
30609
+ inTargetSymbol = false;
30610
+ }
30611
+ continue;
30612
+ }
30613
+ depth += openCount - closeCount;
30614
+ result.push(line);
30615
+ }
30616
+ return result.join(`
30617
+ `).replace(/\n{3,}/g, `
30618
+
30619
+ `);
30620
+ }
30442
30621
  function adaptCommunityComponent(component) {
30443
30622
  const symbolHead = component.symbol.head;
30444
30623
  const cPara = symbolHead?.c_para || {};
@@ -30461,6 +30640,7 @@ function adaptCommunityComponent(component) {
30461
30640
  polylines: component.symbol.polylines,
30462
30641
  polygons: component.symbol.polygons,
30463
30642
  paths: component.symbol.paths,
30643
+ texts: component.symbol.texts || [],
30464
30644
  origin: component.symbol.origin
30465
30645
  },
30466
30646
  footprint: {
@@ -30484,11 +30664,11 @@ function adaptCommunityComponent(component) {
30484
30664
  function createLibraryService() {
30485
30665
  return {
30486
30666
  async install(id, options = {}) {
30487
- const isCommunityComponent = !isLcscId2(id);
30667
+ const isCommunityComponent = !isLcscId3(id);
30488
30668
  const isGlobal = !options.projectPath;
30489
30669
  const paths = isGlobal ? getGlobalLibraryPaths() : getProjectLibraryPaths(options.projectPath);
30490
30670
  let component = null;
30491
- if (isLcscId2(id)) {
30671
+ if (isLcscId3(id)) {
30492
30672
  component = await easyedaClient.getComponentData(id);
30493
30673
  } else {
30494
30674
  const communityComponent = await easyedaCommunityClient.getComponent(id);
@@ -30537,18 +30717,18 @@ function createLibraryService() {
30537
30717
  let easyedaModelsDir;
30538
30718
  if (isGlobal) {
30539
30719
  symbolsDir = paths.symbolsDir;
30540
- footprintDir = join4(paths.footprintsDir, fpLibDir);
30541
- easyedaModelsDir = join4(paths.models3dDir, models3dDirName);
30720
+ footprintDir = join5(paths.footprintsDir, fpLibDir);
30721
+ easyedaModelsDir = join5(paths.models3dDir, models3dDirName);
30542
30722
  } else {
30543
- const librariesDir = join4(options.projectPath, "libraries");
30544
- symbolsDir = join4(librariesDir, "symbols");
30545
- footprintDir = join4(librariesDir, "footprints", fpLibDir);
30546
- easyedaModelsDir = join4(librariesDir, "3dmodels", models3dDirName);
30723
+ const librariesDir = join5(options.projectPath, "libraries");
30724
+ symbolsDir = join5(librariesDir, "symbols");
30725
+ footprintDir = join5(librariesDir, "footprints", fpLibDir);
30726
+ easyedaModelsDir = join5(librariesDir, "3dmodels", models3dDirName);
30547
30727
  }
30548
30728
  models3dDir = easyedaModelsDir;
30549
30729
  await ensureDir(symbolsDir);
30550
30730
  await ensureDir(footprintDir);
30551
- symbolFile = join4(symbolsDir, symLibFile);
30731
+ symbolFile = join5(symbolsDir, symLibFile);
30552
30732
  symbolName = component.info.name.replace(/[^a-zA-Z0-9_-]/g, "_");
30553
30733
  const include3d = options.include3d ?? true;
30554
30734
  let modelRelativePath;
@@ -30557,7 +30737,7 @@ function createLibraryService() {
30557
30737
  const model = await easyedaCommunityClient.get3DModel(component.model3d.uuid, "step");
30558
30738
  if (model) {
30559
30739
  const modelFilename = `${symbolName}.step`;
30560
- modelPath = join4(models3dDir, modelFilename);
30740
+ modelPath = join5(models3dDir, modelFilename);
30561
30741
  await writeBinary(modelPath, model);
30562
30742
  if (isGlobal) {
30563
30743
  modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${models3dDirName}/${modelFilename}`;
@@ -30571,7 +30751,7 @@ function createLibraryService() {
30571
30751
  include3DModel: !!modelRelativePath,
30572
30752
  modelPath: modelRelativePath
30573
30753
  });
30574
- footprintPath = join4(footprintDir, `${symbolName}.kicad_mod`);
30754
+ footprintPath = join5(footprintDir, `${symbolName}.kicad_mod`);
30575
30755
  footprintRef = `${libName}:${symbolName}`;
30576
30756
  await writeText(footprintPath, footprint);
30577
30757
  component.info.package = footprintRef;
@@ -30586,20 +30766,20 @@ function createLibraryService() {
30586
30766
  } else {
30587
30767
  category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
30588
30768
  const symbolLibraryFilename = getLibraryFilename(category);
30589
- symbolFile = join4(paths.symbolsDir, symbolLibraryFilename);
30769
+ symbolFile = join5(paths.symbolsDir, symbolLibraryFilename);
30590
30770
  footprintDir = paths.footprintDir;
30591
30771
  models3dDir = paths.models3dFullDir;
30592
30772
  await ensureDir(paths.symbolsDir);
30593
30773
  await ensureDir(paths.footprintDir);
30594
30774
  symbolName = symbolConverter.getSymbolName(component);
30595
- const include3d = options.include3d ?? false;
30775
+ const include3d = options.include3d ?? true;
30596
30776
  let modelRelativePath;
30597
30777
  if (include3d && component.model3d) {
30598
30778
  await ensureDir(models3dDir);
30599
30779
  const model = await easyedaClient.get3DModel(component.model3d.uuid, "step");
30600
30780
  if (model) {
30601
30781
  const modelFilename = `${symbolName}.step`;
30602
- modelPath = join4(models3dDir, modelFilename);
30782
+ modelPath = join5(models3dDir, modelFilename);
30603
30783
  await writeBinary(modelPath, model);
30604
30784
  modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${MODELS_3D_NAME}/${modelFilename}`;
30605
30785
  }
@@ -30611,7 +30791,7 @@ function createLibraryService() {
30611
30791
  if (footprintResult.type === "reference") {
30612
30792
  footprintRef = footprintResult.reference;
30613
30793
  } else {
30614
- footprintPath = join4(footprintDir, `${footprintResult.name}.kicad_mod`);
30794
+ footprintPath = join5(footprintDir, `${footprintResult.name}.kicad_mod`);
30615
30795
  footprintRef = getFootprintReference(footprintResult.name);
30616
30796
  await writeText(footprintPath, footprintResult.content);
30617
30797
  }
@@ -30624,7 +30804,7 @@ function createLibraryService() {
30624
30804
  }
30625
30805
  let symbolContent;
30626
30806
  let symbolAction;
30627
- if (existsSync3(symbolFile)) {
30807
+ if (existsSync4(symbolFile)) {
30628
30808
  const existingContent = await readFile4(symbolFile, "utf-8");
30629
30809
  if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
30630
30810
  if (options.force) {
@@ -30716,9 +30896,9 @@ function createLibraryService() {
30716
30896
  if (options.category && options.category !== category)
30717
30897
  continue;
30718
30898
  const libraryFilename = getLibraryFilename(category);
30719
- const libraryPath = join4(paths.symbolsDir, libraryFilename);
30899
+ const libraryPath = join5(paths.symbolsDir, libraryFilename);
30720
30900
  const libraryName = `JLC-MCP-${category}`;
30721
- if (existsSync3(libraryPath)) {
30901
+ if (existsSync4(libraryPath)) {
30722
30902
  const components = await parseSymbolLibrary(libraryPath, libraryName, category, paths.models3dFullDir);
30723
30903
  allComponents.push(...components);
30724
30904
  }
@@ -30733,13 +30913,13 @@ function createLibraryService() {
30733
30913
  await ensureGlobalLibraryTables2();
30734
30914
  },
30735
30915
  async getStatus() {
30736
- const version2 = detectKicadVersion2();
30916
+ const version2 = detectKicadVersion();
30737
30917
  const paths = getGlobalLibraryPaths();
30738
- const configDir = getKicadConfigDir2(version2);
30739
- const symLibTablePath = join4(configDir, "sym-lib-table");
30740
- const fpLibTablePath = join4(configDir, "fp-lib-table");
30918
+ const configDir = getKicadConfigDir3(version2);
30919
+ const symLibTablePath = join5(configDir, "sym-lib-table");
30920
+ const fpLibTablePath = join5(configDir, "fp-lib-table");
30741
30921
  let installed = false;
30742
- if (existsSync3(paths.symbolsDir)) {
30922
+ if (existsSync4(paths.symbolsDir)) {
30743
30923
  try {
30744
30924
  const files = await readdir2(paths.symbolsDir);
30745
30925
  installed = files.some((f) => f.endsWith(".kicad_sym"));
@@ -30748,7 +30928,7 @@ function createLibraryService() {
30748
30928
  }
30749
30929
  }
30750
30930
  let linked = false;
30751
- if (existsSync3(symLibTablePath)) {
30931
+ if (existsSync4(symLibTablePath)) {
30752
30932
  try {
30753
30933
  const content = await readFile4(symLibTablePath, "utf-8");
30754
30934
  linked = content.includes("JLC-MCP");
@@ -30774,8 +30954,8 @@ function createLibraryService() {
30774
30954
  },
30775
30955
  async isEasyEDAInstalled(componentName) {
30776
30956
  const paths = getGlobalLibraryPaths();
30777
- const easyedaLibPath = join4(paths.symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME2);
30778
- if (!existsSync3(easyedaLibPath)) {
30957
+ const easyedaLibPath = join5(paths.symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME2);
30958
+ if (!existsSync4(easyedaLibPath)) {
30779
30959
  return false;
30780
30960
  }
30781
30961
  try {
@@ -30786,6 +30966,85 @@ function createLibraryService() {
30786
30966
  } catch {
30787
30967
  return false;
30788
30968
  }
30969
+ },
30970
+ async regenerate(options = {}) {
30971
+ const include3d = options.include3d ?? true;
30972
+ const installed = await this.listInstalled({ projectPath: options.projectPath });
30973
+ const results = {
30974
+ total: installed.length,
30975
+ success: 0,
30976
+ failed: 0,
30977
+ components: []
30978
+ };
30979
+ for (let i = 0;i < installed.length; i++) {
30980
+ const component = installed[i];
30981
+ options.onProgress?.(i + 1, installed.length, component, "start");
30982
+ try {
30983
+ await this.install(component.lcscId, {
30984
+ projectPath: options.projectPath,
30985
+ include3d,
30986
+ force: true
30987
+ });
30988
+ results.success++;
30989
+ results.components.push({
30990
+ id: component.lcscId,
30991
+ name: component.name,
30992
+ status: "success"
30993
+ });
30994
+ options.onProgress?.(i + 1, installed.length, component, "success");
30995
+ } catch (error2) {
30996
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
30997
+ results.failed++;
30998
+ results.components.push({
30999
+ id: component.lcscId,
31000
+ name: component.name,
31001
+ status: "failed",
31002
+ error: errorMessage
31003
+ });
31004
+ options.onProgress?.(i + 1, installed.length, component, "error", errorMessage);
31005
+ }
31006
+ }
31007
+ return results;
31008
+ },
31009
+ async remove(lcscId, options = {}) {
31010
+ const paths = options.projectPath ? getProjectLibraryPaths(options.projectPath) : getGlobalLibraryPaths();
31011
+ const installed = await this.listInstalled({ projectPath: options.projectPath });
31012
+ const component = installed.find((c) => c.lcscId === lcscId);
31013
+ if (!component) {
31014
+ throw new Error(`Component ${lcscId} not found in library`);
31015
+ }
31016
+ const result = {
31017
+ success: false,
31018
+ lcscId,
31019
+ symbolRemoved: false,
31020
+ footprintRemoved: false,
31021
+ model3dRemoved: false
31022
+ };
31023
+ const libraryFilename = getLibraryFilename(component.category);
31024
+ const symbolLibPath = join5(paths.symbolsDir, libraryFilename);
31025
+ if (existsSync4(symbolLibPath)) {
31026
+ const content = await readFile4(symbolLibPath, "utf-8");
31027
+ const newContent = removeSymbolFromLibrary(content, component.name);
31028
+ await writeText(symbolLibPath, newContent);
31029
+ result.symbolRemoved = true;
31030
+ }
31031
+ if (component.footprintRef?.startsWith("JLC-MCP:")) {
31032
+ const fpName = component.footprintRef.split(":")[1];
31033
+ const footprintPath = join5(paths.footprintDir, `${fpName}.kicad_mod`);
31034
+ if (existsSync4(footprintPath)) {
31035
+ await unlink(footprintPath);
31036
+ result.footprintRemoved = true;
31037
+ }
31038
+ }
31039
+ if (component.has3dModel) {
31040
+ const modelPath = join5(paths.models3dFullDir, `${component.name}.step`);
31041
+ if (existsSync4(modelPath)) {
31042
+ await unlink(modelPath);
31043
+ result.model3dRemoved = true;
31044
+ }
31045
+ }
31046
+ result.success = result.symbolRemoved;
31047
+ return result;
30789
31048
  }
30790
31049
  };
30791
31050
  }
@@ -30801,16 +31060,16 @@ function getHtmlPage() {
30801
31060
  return htmlCache;
30802
31061
  try {
30803
31062
  const possiblePaths = [
30804
- join5(__dirname2, "assets/search.html"),
30805
- join5(__dirname2, "../dist/assets/search.html"),
30806
- join5(__dirname2, "../assets/search.html"),
30807
- join5(__dirname2, "../assets/search-built.html"),
30808
- join5(process.cwd(), "dist/assets/search.html"),
30809
- join5(process.cwd(), "packages/core/dist/assets/search.html"),
30810
- join5(__dirname2, "../../dist/assets/search.html"),
30811
- join5(__dirname2, "../../../core/dist/assets/search.html"),
30812
- join5(__dirname2, "../../../../packages/core/dist/assets/search.html"),
30813
- join5(__dirname2, "../../node_modules/@jlcpcb/core/dist/assets/search.html")
31063
+ join6(__dirname2, "assets/search.html"),
31064
+ join6(__dirname2, "../dist/assets/search.html"),
31065
+ join6(__dirname2, "../assets/search.html"),
31066
+ join6(__dirname2, "../assets/search-built.html"),
31067
+ join6(process.cwd(), "dist/assets/search.html"),
31068
+ join6(process.cwd(), "packages/core/dist/assets/search.html"),
31069
+ join6(__dirname2, "../../dist/assets/search.html"),
31070
+ join6(__dirname2, "../../../core/dist/assets/search.html"),
31071
+ join6(__dirname2, "../../../../packages/core/dist/assets/search.html"),
31072
+ join6(__dirname2, "../../node_modules/@jlcpcb/core/dist/assets/search.html")
30814
31073
  ];
30815
31074
  for (const path of possiblePaths) {
30816
31075
  try {
@@ -31001,17 +31260,51 @@ function startHttpServer(options = {}) {
31001
31260
  });
31002
31261
  return port;
31003
31262
  }
31263
+ var EE_TO_MM3 = 10 * 0.0254;
31264
+ var EE_TO_MM4 = 10 * 0.0254;
31265
+ var EE_TO_MM5 = 10 * 0.0254;
31266
+
31267
+ // src/services.ts
31268
+ var componentService = null;
31269
+ var libraryService = null;
31270
+ function getComponentService() {
31271
+ if (!componentService) {
31272
+ componentService = createComponentService();
31273
+ }
31274
+ return componentService;
31275
+ }
31276
+ function getLibraryService() {
31277
+ if (!libraryService) {
31278
+ libraryService = createLibraryService();
31279
+ }
31280
+ return libraryService;
31281
+ }
31004
31282
 
31005
31283
  // src/tools/search.ts
31006
- var searchComponentsTool = {
31284
+ var componentSearchTool = {
31007
31285
  name: "component_search",
31008
- description: "Search the JLC/JLCPCB component database by keyword. Returns components with LCSC part numbers (JLC's preferred supplier for assembly). Includes prices, stock levels, and whether parts are in the Basic Parts Library (lower assembly cost).",
31286
+ description: `Search for electronic components by keyword.
31287
+
31288
+ Sources:
31289
+ - "lcsc" (default): Search JLCPCB/LCSC official parts library. Returns components with LCSC IDs available for JLC assembly.
31290
+ - "community": Search EasyEDA community library for user-contributed parts (Arduino modules, XIAO, custom breakouts).
31291
+
31292
+ Returns full component details for selection including:
31293
+ - LCSC ID, manufacturer part number, manufacturer name
31294
+ - Description, package, datasheet URL
31295
+ - Price, stock, library type (basic/extended)
31296
+ - Key attributes (resistance, voltage, etc.)`,
31009
31297
  inputSchema: {
31010
31298
  type: "object",
31011
31299
  properties: {
31012
31300
  query: {
31013
31301
  type: "string",
31014
- description: 'Search query (e.g., "ESP32", "STM32F103", "0805 100nF")'
31302
+ description: 'Search query (e.g., "ESP32", "STM32F103", "0805 100nF", "XIAO RP2040")'
31303
+ },
31304
+ source: {
31305
+ type: "string",
31306
+ enum: ["lcsc", "community"],
31307
+ description: 'Search source: "lcsc" for official parts (default), "community" for EasyEDA user-contributed'
31015
31308
  },
31016
31309
  limit: {
31017
31310
  type: "number",
@@ -31019,11 +31312,11 @@ var searchComponentsTool = {
31019
31312
  },
31020
31313
  in_stock: {
31021
31314
  type: "boolean",
31022
- description: "Only show in-stock items (default: false)"
31315
+ description: "Only show in-stock items (default: false, LCSC only)"
31023
31316
  },
31024
31317
  basic_only: {
31025
31318
  type: "boolean",
31026
- description: "Only show JLCPCB Basic Parts Library components (lower assembly cost, default: false)"
31319
+ description: "Only show JLCPCB Basic Parts Library components (lower assembly cost, LCSC only)"
31027
31320
  }
31028
31321
  },
31029
31322
  required: ["query"]
@@ -31031,112 +31324,64 @@ var searchComponentsTool = {
31031
31324
  };
31032
31325
  var SearchParamsSchema = exports_external.object({
31033
31326
  query: exports_external.string().min(1),
31327
+ source: exports_external.enum(["lcsc", "community"]).default("lcsc"),
31034
31328
  limit: exports_external.number().min(1).max(50).default(10),
31035
31329
  in_stock: exports_external.boolean().optional(),
31036
31330
  basic_only: exports_external.boolean().optional()
31037
31331
  });
31038
- async function handleSearchComponents(args) {
31332
+ async function handleComponentSearch(args) {
31039
31333
  const params = SearchParamsSchema.parse(args);
31040
- const results = await jlcClient.search(params.query, {
31334
+ const results = await getComponentService().search(params.query, {
31335
+ source: params.source,
31041
31336
  limit: params.limit,
31042
31337
  inStock: params.in_stock,
31043
31338
  basicOnly: params.basic_only
31044
31339
  });
31045
- return {
31046
- content: [{
31047
- type: "text",
31048
- text: JSON.stringify(results, null, 2)
31049
- }]
31050
- };
31051
- }
31052
-
31053
- // src/tools/details.ts
31054
- var getComponentTool = {
31055
- name: "component_get",
31056
- description: "Get detailed component information by LCSC part number (e.g., C2040). LCSC is JLC PCB's preferred supplier - components with LCSC IDs are available for JLC assembly. Returns symbol pins, footprint pads, manufacturer info, datasheet URL, and 3D model reference.",
31057
- inputSchema: {
31058
- type: "object",
31059
- properties: {
31060
- lcsc_id: {
31061
- type: "string",
31062
- description: "LCSC part number (e.g., C2040, C14663)"
31063
- }
31064
- },
31065
- required: ["lcsc_id"]
31066
- }
31067
- };
31068
- var GetComponentParamsSchema = exports_external.object({
31069
- lcsc_id: exports_external.string().regex(/^C\d+$/, "Invalid LCSC part number")
31070
- });
31071
- async function handleGetComponent(args) {
31072
- const params = GetComponentParamsSchema.parse(args);
31073
- const component = await easyedaClient.getComponentData(params.lcsc_id);
31074
- if (!component) {
31340
+ if (results.length === 0) {
31075
31341
  return {
31076
31342
  content: [{
31077
31343
  type: "text",
31078
- text: `Component ${params.lcsc_id} not found`
31079
- }],
31080
- isError: true
31344
+ text: JSON.stringify({
31345
+ success: true,
31346
+ query: params.query,
31347
+ source: params.source,
31348
+ count: 0,
31349
+ results: [],
31350
+ hint: params.source === "lcsc" ? 'Try searching with source: "community" for user-contributed parts' : 'Try searching with source: "lcsc" for official JLCPCB parts'
31351
+ })
31352
+ }]
31081
31353
  };
31082
31354
  }
31083
- const summary = {
31084
- info: component.info,
31085
- symbol: {
31086
- pin_count: component.symbol.pins.length,
31087
- pins: component.symbol.pins
31088
- },
31089
- footprint: {
31090
- name: component.footprint.name,
31091
- type: component.footprint.type,
31092
- pad_count: component.footprint.pads.length,
31093
- pads: component.footprint.pads
31094
- },
31095
- has_3d_model: !!component.model3d,
31096
- model_3d_uuid: component.model3d?.uuid
31097
- };
31355
+ const compactResults = results.map((r) => ({
31356
+ lcsc_id: r.lcscId,
31357
+ name: r.name,
31358
+ manufacturer: r.manufacturer,
31359
+ description: r.description,
31360
+ package: r.package,
31361
+ datasheet: r.datasheetPdf,
31362
+ stock: r.stock,
31363
+ price: r.price,
31364
+ library_type: r.libraryType,
31365
+ category: r.category,
31366
+ attributes: r.attributes
31367
+ }));
31098
31368
  return {
31099
31369
  content: [{
31100
31370
  type: "text",
31101
- text: JSON.stringify(summary, null, 2)
31371
+ text: JSON.stringify({
31372
+ success: true,
31373
+ query: params.query,
31374
+ source: params.source,
31375
+ count: results.length,
31376
+ results: compactResults
31377
+ })
31102
31378
  }]
31103
31379
  };
31104
31380
  }
31105
-
31106
31381
  // src/tools/library.ts
31107
- var libraryService = createLibraryService();
31108
- var componentService = createComponentService();
31109
- var getSymbolKicadTool = {
31110
- name: "library_get_symbol",
31111
- description: "Get a KiCad-compatible symbol definition by LCSC part number. Returns the symbol in .kicad_sym format. LCSC is JLC PCB's preferred supplier for assembly.",
31112
- inputSchema: {
31113
- type: "object",
31114
- properties: {
31115
- lcsc_id: {
31116
- type: "string",
31117
- description: "LCSC part number (e.g., C2040)"
31118
- }
31119
- },
31120
- required: ["lcsc_id"]
31121
- }
31122
- };
31123
- var getFootprintKicadTool = {
31124
- name: "library_get_footprint",
31125
- description: "Get a KiCad-compatible footprint definition by LCSC part number. Returns the footprint in .kicad_mod format. LCSC is JLC PCB's preferred supplier for assembly.",
31126
- inputSchema: {
31127
- type: "object",
31128
- properties: {
31129
- lcsc_id: {
31130
- type: "string",
31131
- description: "LCSC part number (e.g., C2040)"
31132
- }
31133
- },
31134
- required: ["lcsc_id"]
31135
- }
31136
- };
31137
- var fetchLibraryTool = {
31138
- name: "library_fetch",
31139
- description: `Fetch a component and add it to KiCad libraries.
31382
+ var libraryInstallTool = {
31383
+ name: "library_install",
31384
+ description: `Install a component to KiCad libraries.
31140
31385
 
31141
31386
  Accepts:
31142
31387
  - LCSC part numbers (e.g., C2040) → global JLC-MCP libraries
@@ -31146,11 +31391,7 @@ LCSC components are routed to category-based global libraries:
31146
31391
  - JLC-MCP-Resistors.kicad_sym, JLC-MCP-Capacitors.kicad_sym, JLC-MCP-ICs.kicad_sym, etc.
31147
31392
  - Stored at ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
31148
31393
 
31149
- EasyEDA community components are stored project-locally:
31150
- - <project>/libraries/symbols/EasyEDA.kicad_sym
31151
- - <project>/libraries/footprints/EasyEDA.pretty/
31152
-
31153
- Returns symbol_ref and footprint_ref for immediate use with add_schematic_component.`,
31394
+ Returns symbol_ref and footprint_ref for use with schematic placement.`,
31154
31395
  inputSchema: {
31155
31396
  type: "object",
31156
31397
  properties: {
@@ -31164,89 +31405,47 @@ Returns symbol_ref and footprint_ref for immediate use with add_schematic_compon
31164
31405
  },
31165
31406
  include_3d: {
31166
31407
  type: "boolean",
31167
- description: "Include 3D model if available (default: false for LCSC, true for EasyEDA)"
31408
+ description: "Include 3D model if available (default: false)"
31409
+ },
31410
+ force: {
31411
+ type: "boolean",
31412
+ description: "Reinstall even if already exists (default: false)"
31168
31413
  }
31169
31414
  },
31170
31415
  required: ["id"]
31171
31416
  }
31172
31417
  };
31173
- var get3DModelTool = {
31174
- name: "library_get_3d_model",
31175
- description: "Download a 3D model for a component. Requires the model UUID from component_get. Returns the model as base64-encoded STEP data.",
31418
+ var libraryGetComponentTool = {
31419
+ name: "library_get_component",
31420
+ description: `Get metadata for an installed component's symbol and footprint.
31421
+
31422
+ Returns symbol reference, footprint reference, file paths, and pin/pad counts.
31423
+ Does NOT return full file contents to minimize token usage.
31424
+
31425
+ Use this to verify installation or get references for schematic placement.`,
31176
31426
  inputSchema: {
31177
31427
  type: "object",
31178
31428
  properties: {
31179
- uuid: {
31180
- type: "string",
31181
- description: "3D model UUID from component_get result"
31182
- },
31183
- format: {
31429
+ id: {
31184
31430
  type: "string",
31185
- enum: ["step", "obj"],
31186
- description: 'Model format: "step" or "obj" (default: step)'
31431
+ description: "LCSC part number (e.g., C2040)"
31187
31432
  }
31188
31433
  },
31189
- required: ["uuid"]
31434
+ required: ["id"]
31190
31435
  }
31191
31436
  };
31192
- var LibraryParamsSchema = exports_external.object({
31193
- lcsc_id: exports_external.string().regex(/^C\d+$/, "Invalid LCSC part number")
31194
- });
31195
- var FetchLibraryParamsSchema = exports_external.object({
31437
+ var LibraryInstallParamsSchema = exports_external.object({
31196
31438
  id: exports_external.string().min(1),
31197
- project_path: exports_external.string().min(1).optional(),
31198
- include_3d: exports_external.boolean().optional()
31439
+ project_path: SafePathSchema.optional(),
31440
+ include_3d: exports_external.boolean().optional(),
31441
+ force: exports_external.boolean().optional()
31199
31442
  });
31200
- var Model3DParamsSchema = exports_external.object({
31201
- uuid: exports_external.string().min(1),
31202
- format: exports_external.enum(["step", "obj"]).default("step")
31443
+ var LibraryGetComponentParamsSchema = exports_external.object({
31444
+ id: LCSCPartNumberSchema
31203
31445
  });
31204
- async function handleGetSymbolKicad(args) {
31205
- const params = LibraryParamsSchema.parse(args);
31206
- const component = await easyedaClient.getComponentData(params.lcsc_id);
31207
- if (!component) {
31208
- return {
31209
- content: [{
31210
- type: "text",
31211
- text: `Component ${params.lcsc_id} not found`
31212
- }],
31213
- isError: true
31214
- };
31215
- }
31216
- const symbol = symbolConverter.convert(component);
31217
- return {
31218
- content: [{
31219
- type: "text",
31220
- text: symbol
31221
- }]
31222
- };
31223
- }
31224
- async function handleGetFootprintKicad(args) {
31225
- const params = LibraryParamsSchema.parse(args);
31226
- const component = await easyedaClient.getComponentData(params.lcsc_id);
31227
- if (!component) {
31228
- return {
31229
- content: [{
31230
- type: "text",
31231
- text: `Component ${params.lcsc_id} not found`
31232
- }],
31233
- isError: true
31234
- };
31235
- }
31236
- const footprint = footprintConverter.convert(component);
31237
- return {
31238
- content: [{
31239
- type: "text",
31240
- text: footprint
31241
- }]
31242
- };
31243
- }
31244
- function isLcscId3(id) {
31245
- return /^C\d+$/.test(id);
31246
- }
31247
- async function handleFetchLibrary(args) {
31248
- const params = FetchLibraryParamsSchema.parse(args);
31249
- const isCommunityComponent = !isLcscId3(params.id);
31446
+ async function handleLibraryInstall(args) {
31447
+ const params = LibraryInstallParamsSchema.parse(args);
31448
+ const isCommunityComponent = !isLcscId(params.id);
31250
31449
  if (isCommunityComponent && !params.project_path) {
31251
31450
  return {
31252
31451
  content: [{
@@ -31262,9 +31461,10 @@ async function handleFetchLibrary(args) {
31262
31461
  };
31263
31462
  }
31264
31463
  try {
31265
- const result = await libraryService.install(params.id, {
31464
+ const result = await getLibraryService().install(params.id, {
31266
31465
  projectPath: params.project_path,
31267
- include3d: params.include_3d
31466
+ include3d: params.include_3d,
31467
+ force: params.force
31268
31468
  });
31269
31469
  return {
31270
31470
  content: [{
@@ -31272,22 +31472,23 @@ async function handleFetchLibrary(args) {
31272
31472
  text: JSON.stringify({
31273
31473
  success: true,
31274
31474
  id: params.id,
31275
- source: result.source,
31276
- storage_mode: result.storageMode,
31277
- category: result.category,
31278
- symbol_name: result.symbolName,
31475
+ installed: true,
31279
31476
  symbol_ref: result.symbolRef,
31280
31477
  footprint_ref: result.footprintRef,
31281
- footprint_type: result.footprintType,
31282
- datasheet: result.datasheet,
31478
+ category: result.category,
31283
31479
  files: {
31284
31480
  symbol_library: result.files.symbolLibrary,
31285
31481
  footprint: result.files.footprint,
31286
31482
  model_3d: result.files.model3d
31287
31483
  },
31288
- symbol_action: result.symbolAction,
31289
- validation_data: result.validationData
31290
- }, null, 2)
31484
+ validation: result.validationData ? {
31485
+ pin_pad_match: result.validationData.checks.pin_pad_count_match,
31486
+ pin_count: result.validationData.symbol.pin_count,
31487
+ pad_count: result.validationData.footprint.pad_count,
31488
+ has_power_pins: result.validationData.checks.has_power_pins,
31489
+ has_ground_pins: result.validationData.checks.has_ground_pins
31490
+ } : undefined
31491
+ })
31291
31492
  }]
31292
31493
  };
31293
31494
  } catch (error2) {
@@ -31297,76 +31498,202 @@ async function handleFetchLibrary(args) {
31297
31498
  text: JSON.stringify({
31298
31499
  success: false,
31299
31500
  error: error2 instanceof Error ? error2.message : "Unknown error",
31300
- id: params.id,
31301
- source: isCommunityComponent ? "easyeda_community" : "lcsc"
31501
+ id: params.id
31302
31502
  })
31303
31503
  }],
31304
31504
  isError: true
31305
31505
  };
31306
31506
  }
31307
31507
  }
31308
- async function handleGet3DModel(args) {
31309
- const params = Model3DParamsSchema.parse(args);
31310
- const model = await easyedaClient.get3DModel(params.uuid, params.format);
31311
- if (!model) {
31508
+ async function handleLibraryGetComponent(args) {
31509
+ const params = LibraryGetComponentParamsSchema.parse(args);
31510
+ try {
31511
+ const status = await getLibraryService().getStatus();
31512
+ if (!status.installed) {
31513
+ return {
31514
+ content: [{
31515
+ type: "text",
31516
+ text: JSON.stringify({
31517
+ success: false,
31518
+ error: "JLC-MCP libraries not installed. Run library_install first.",
31519
+ id: params.id
31520
+ })
31521
+ }],
31522
+ isError: true
31523
+ };
31524
+ }
31525
+ const installed = await getLibraryService().listInstalled();
31526
+ const component = installed.find((c) => c.lcscId === params.id);
31527
+ if (!component) {
31528
+ return {
31529
+ content: [{
31530
+ type: "text",
31531
+ text: JSON.stringify({
31532
+ success: false,
31533
+ error: `Component ${params.id} is not installed`,
31534
+ id: params.id,
31535
+ hint: "Use library_install to add this component first"
31536
+ })
31537
+ }],
31538
+ isError: true
31539
+ };
31540
+ }
31541
+ return {
31542
+ content: [{
31543
+ type: "text",
31544
+ text: JSON.stringify({
31545
+ success: true,
31546
+ id: params.id,
31547
+ installed: true,
31548
+ symbol_ref: component.symbolRef,
31549
+ footprint_ref: component.footprintRef,
31550
+ category: component.category,
31551
+ symbol_library: component.library,
31552
+ name: component.name,
31553
+ has_3d_model: component.has3dModel
31554
+ })
31555
+ }]
31556
+ };
31557
+ } catch (error2) {
31312
31558
  return {
31313
31559
  content: [{
31314
31560
  type: "text",
31315
- text: `3D model ${params.uuid} not found`
31561
+ text: JSON.stringify({
31562
+ success: false,
31563
+ error: error2 instanceof Error ? error2.message : "Unknown error",
31564
+ id: params.id
31565
+ })
31316
31566
  }],
31317
31567
  isError: true
31318
31568
  };
31319
31569
  }
31570
+ }
31571
+
31572
+ // src/tools/batch.ts
31573
+ var libraryBatchInstallTool = {
31574
+ name: "library_batch_install",
31575
+ description: `Install multiple components to KiCad libraries in a single call.
31576
+
31577
+ Accepts up to 10 LCSC part numbers. Components are installed in parallel.
31578
+ Returns a summary of installed, skipped (already installed), and failed components.
31579
+
31580
+ Use this when you need to install a bill of materials or multiple components at once.`,
31581
+ inputSchema: {
31582
+ type: "object",
31583
+ properties: {
31584
+ ids: {
31585
+ type: "array",
31586
+ items: { type: "string" },
31587
+ maxItems: 10,
31588
+ description: 'Array of LCSC part numbers (e.g., ["C2040", "C5446", "C14663"])'
31589
+ },
31590
+ force: {
31591
+ type: "boolean",
31592
+ description: "Reinstall even if already exists (default: false)"
31593
+ },
31594
+ include_3d: {
31595
+ type: "boolean",
31596
+ description: "Include 3D models if available (default: false)"
31597
+ }
31598
+ },
31599
+ required: ["ids"]
31600
+ }
31601
+ };
31602
+ var BatchInstallParamsSchema = exports_external.object({
31603
+ ids: exports_external.array(LCSCPartNumberSchema).min(1).max(10),
31604
+ force: exports_external.boolean().optional(),
31605
+ include_3d: exports_external.boolean().optional()
31606
+ });
31607
+ async function handleLibraryBatchInstall(args) {
31608
+ const params = BatchInstallParamsSchema.parse(args);
31609
+ const results = [];
31610
+ let installed = 0;
31611
+ let skipped = 0;
31612
+ let failed = 0;
31613
+ const installPromises = params.ids.map(async (id) => {
31614
+ try {
31615
+ const result = await getLibraryService().install(id, {
31616
+ include3d: params.include_3d,
31617
+ force: params.force
31618
+ });
31619
+ if (result.symbolAction === "exists") {
31620
+ return {
31621
+ id,
31622
+ status: "skipped",
31623
+ symbol_ref: result.symbolRef,
31624
+ footprint_ref: result.footprintRef,
31625
+ reason: "already installed"
31626
+ };
31627
+ }
31628
+ return {
31629
+ id,
31630
+ status: "installed",
31631
+ symbol_ref: result.symbolRef,
31632
+ footprint_ref: result.footprintRef
31633
+ };
31634
+ } catch (error2) {
31635
+ return {
31636
+ id,
31637
+ status: "failed",
31638
+ error: error2 instanceof Error ? error2.message : "Unknown error"
31639
+ };
31640
+ }
31641
+ });
31642
+ const installResults = await Promise.all(installPromises);
31643
+ for (const result of installResults) {
31644
+ results.push(result);
31645
+ if (result.status === "installed")
31646
+ installed++;
31647
+ else if (result.status === "skipped")
31648
+ skipped++;
31649
+ else
31650
+ failed++;
31651
+ }
31320
31652
  return {
31321
31653
  content: [{
31322
31654
  type: "text",
31323
- text: `3D model downloaded (${model.length} bytes, ${params.format.toUpperCase()} format)
31324
-
31325
- Base64 data:
31326
- ${model.toString("base64").slice(0, 500)}...`
31655
+ text: JSON.stringify({
31656
+ success: failed < params.ids.length,
31657
+ summary: {
31658
+ total: params.ids.length,
31659
+ installed,
31660
+ skipped,
31661
+ failed
31662
+ },
31663
+ results
31664
+ })
31327
31665
  }]
31328
31666
  };
31329
31667
  }
31330
31668
 
31331
31669
  // src/tools/library-update.ts
31332
- import { existsSync as existsSync4, readdirSync } from "fs";
31670
+ import { existsSync as existsSync5, readdirSync } from "fs";
31333
31671
  import { readFile as readFile5 } from "fs/promises";
31334
- import { homedir as homedir3, platform as platform3 } from "os";
31335
- import { join as join6 } from "path";
31336
- var KICAD_VERSIONS = ["9.0", "8.0"];
31672
+ import { homedir as homedir4, platform as platform4 } from "os";
31673
+ import { join as join7 } from "path";
31337
31674
  var LIBRARY_NAMESPACE3 = "jlc_mcp";
31338
- function detectKicadVersion3() {
31339
- const home = homedir3();
31340
- const baseDir = join6(home, "Documents", "KiCad");
31341
- for (const version2 of KICAD_VERSIONS) {
31342
- if (existsSync4(join6(baseDir, version2))) {
31343
- return version2;
31344
- }
31345
- }
31346
- return "9.0";
31347
- }
31348
31675
  function getLibraryPaths(projectPath) {
31349
31676
  if (projectPath) {
31350
- const librariesDir = join6(projectPath, "libraries");
31677
+ const librariesDir = join7(projectPath, "libraries");
31351
31678
  return {
31352
- symbolsDir: join6(librariesDir, "symbols"),
31353
- footprintDir: join6(librariesDir, "footprints", getFootprintDirName()),
31354
- models3dDir: join6(librariesDir, "3dmodels", get3DModelsDirName())
31679
+ symbolsDir: join7(librariesDir, "symbols"),
31680
+ footprintDir: join7(librariesDir, "footprints", getFootprintDirName()),
31681
+ models3dDir: join7(librariesDir, "3dmodels", get3DModelsDirName())
31355
31682
  };
31356
31683
  }
31357
- const home = homedir3();
31358
- const version2 = detectKicadVersion3();
31359
- const plat = platform3();
31684
+ const home = homedir4();
31685
+ const version2 = detectKicadVersion();
31686
+ const plat = platform4();
31360
31687
  let base;
31361
31688
  if (plat === "linux") {
31362
- base = join6(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE3);
31689
+ base = join7(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE3);
31363
31690
  } else {
31364
- base = join6(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE3);
31691
+ base = join7(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE3);
31365
31692
  }
31366
31693
  return {
31367
- symbolsDir: join6(base, "symbols"),
31368
- footprintDir: join6(base, "footprints", getFootprintDirName()),
31369
- models3dDir: join6(base, "3dmodels", get3DModelsDirName())
31694
+ symbolsDir: join7(base, "symbols"),
31695
+ footprintDir: join7(base, "footprints", getFootprintDirName()),
31696
+ models3dDir: join7(base, "3dmodels", get3DModelsDirName())
31370
31697
  };
31371
31698
  }
31372
31699
  function extractLcscIdsFromLibrary(content) {
@@ -31387,12 +31714,12 @@ function generateEmptyLibrary() {
31387
31714
  `;
31388
31715
  }
31389
31716
  function findJlcLibraries(symbolsDir) {
31390
- if (!existsSync4(symbolsDir)) {
31717
+ if (!existsSync5(symbolsDir)) {
31391
31718
  return [];
31392
31719
  }
31393
31720
  try {
31394
31721
  const files = readdirSync(symbolsDir);
31395
- return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) => join6(symbolsDir, f));
31722
+ return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) => join7(symbolsDir, f));
31396
31723
  } catch {
31397
31724
  return [];
31398
31725
  }
@@ -31459,7 +31786,7 @@ async function handleUpdateLibrary(args) {
31459
31786
  libraries_to_create: allCategories.map((cat) => ({
31460
31787
  category: cat,
31461
31788
  filename: getLibraryFilename(cat),
31462
- path: join6(paths.symbolsDir, getLibraryFilename(cat))
31789
+ path: join7(paths.symbolsDir, getLibraryFilename(cat))
31463
31790
  }))
31464
31791
  }, null, 2)
31465
31792
  }]
@@ -31472,7 +31799,7 @@ async function handleUpdateLibrary(args) {
31472
31799
  const createdLibraries = [];
31473
31800
  for (const category of allCategories) {
31474
31801
  const filename = getLibraryFilename(category);
31475
- const filepath = join6(paths.symbolsDir, filename);
31802
+ const filepath = join7(paths.symbolsDir, filename);
31476
31803
  await writeText(filepath, emptyContent);
31477
31804
  createdLibraries.push(filepath);
31478
31805
  }
@@ -31549,7 +31876,7 @@ async function handleUpdateLibrary(args) {
31549
31876
  if (!params.dry_run && footprintResult.type === "generated") {
31550
31877
  await ensureDir(paths.footprintDir);
31551
31878
  const footprintName = footprintResult.name + "_" + lcscId;
31552
- const footprintPath = join6(paths.footprintDir, `${footprintName}.kicad_mod`);
31879
+ const footprintPath = join7(paths.footprintDir, `${footprintName}.kicad_mod`);
31553
31880
  await writeText(footprintPath, footprintResult.content);
31554
31881
  }
31555
31882
  results.push({
@@ -31576,7 +31903,7 @@ async function handleUpdateLibrary(args) {
31576
31903
  await ensureDir(paths.symbolsDir);
31577
31904
  for (const [category, entries] of categorySymbols) {
31578
31905
  const filename = getLibraryFilename(category);
31579
- const filepath = join6(paths.symbolsDir, filename);
31906
+ const filepath = join7(paths.symbolsDir, filename);
31580
31907
  const header = `(kicad_symbol_lib
31581
31908
  (version 20241209)
31582
31909
  (generator "jlc-mcp")
@@ -31604,53 +31931,41 @@ async function handleUpdateLibrary(args) {
31604
31931
  success: true,
31605
31932
  dry_run: params.dry_run,
31606
31933
  summary: {
31607
- total_components: allLcscIds.size,
31934
+ total: allLcscIds.size,
31608
31935
  updated: successful.length,
31609
- failed: failed.length,
31610
- libraries_generated: categorySymbols.size
31936
+ failed: failed.length
31611
31937
  },
31612
31938
  by_category: Object.fromEntries(byCategory),
31613
31939
  footprint_stats: footprintStats,
31614
- libraries_written: params.dry_run ? [] : Array.from(categorySymbols.keys()).map((cat) => join6(paths.symbolsDir, getLibraryFilename(cat))),
31615
- failed_components: failed.map((f) => ({
31940
+ failed_sample: failed.slice(0, 5).map((f) => ({
31616
31941
  lcsc_id: f.lcscId,
31617
31942
  error: f.error
31618
- }))
31619
- }, null, 2)
31943
+ })),
31944
+ has_more_failures: failed.length > 5
31945
+ })
31620
31946
  }]
31621
31947
  };
31622
31948
  }
31623
31949
 
31624
31950
  // src/tools/library-fix.ts
31625
- import { existsSync as existsSync6 } from "fs";
31951
+ import { existsSync as existsSync7 } from "fs";
31626
31952
  import { readFile as readFile7 } from "fs/promises";
31627
- import { homedir as homedir5 } from "os";
31628
- import { join as join8 } from "path";
31629
- var KICAD_VERSIONS4 = ["9.0", "8.0"];
31630
- function detectKicadVersion5() {
31631
- const home = homedir5();
31632
- const baseDir = join8(home, "Documents", "KiCad");
31633
- for (const version2 of KICAD_VERSIONS4) {
31634
- if (existsSync6(join8(baseDir, version2))) {
31635
- return version2;
31636
- }
31637
- }
31638
- return "9.0";
31639
- }
31953
+ import { homedir as homedir6 } from "os";
31954
+ import { join as join9 } from "path";
31640
31955
  function getLibraryPaths2(projectPath) {
31641
31956
  if (projectPath) {
31642
- const librariesDir = join8(projectPath, "libraries");
31957
+ const librariesDir = join9(projectPath, "libraries");
31643
31958
  return {
31644
- symbolsDir: join8(librariesDir, "symbols"),
31645
- footprintDir: join8(librariesDir, "footprints", getFootprintDirName())
31959
+ symbolsDir: join9(librariesDir, "symbols"),
31960
+ footprintDir: join9(librariesDir, "footprints", getFootprintDirName())
31646
31961
  };
31647
31962
  }
31648
- const home = homedir5();
31649
- const version2 = detectKicadVersion5();
31650
- const base = join8(home, "Documents", "KiCad", version2);
31963
+ const home = homedir6();
31964
+ const version2 = detectKicadVersion();
31965
+ const base = join9(home, "Documents", "KiCad", version2);
31651
31966
  return {
31652
- symbolsDir: join8(base, "symbols"),
31653
- footprintDir: join8(base, "footprints", getFootprintDirName())
31967
+ symbolsDir: join9(base, "symbols"),
31968
+ footprintDir: join9(base, "footprints", getFootprintDirName())
31654
31969
  };
31655
31970
  }
31656
31971
  var PinElectricalType = exports_external.enum([
@@ -31818,8 +32133,8 @@ async function handleFixLibrary(args) {
31818
32133
  }
31819
32134
  const category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
31820
32135
  const symbolLibraryFilename = getLibraryFilename(category);
31821
- const symbolFile = join8(paths.symbolsDir, symbolLibraryFilename);
31822
- if (!existsSync6(symbolFile) && !params.force) {
32136
+ const symbolFile = join9(paths.symbolsDir, symbolLibraryFilename);
32137
+ if (!existsSync7(symbolFile) && !params.force) {
31823
32138
  return {
31824
32139
  content: [{
31825
32140
  type: "text",
@@ -31833,7 +32148,7 @@ async function handleFixLibrary(args) {
31833
32148
  isError: true
31834
32149
  };
31835
32150
  }
31836
- if (existsSync6(symbolFile) && !params.force) {
32151
+ if (existsSync7(symbolFile) && !params.force) {
31837
32152
  const existingContent = await readFile7(symbolFile, "utf-8");
31838
32153
  if (!symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
31839
32154
  return {
@@ -31920,14 +32235,14 @@ async function handleFixLibrary(args) {
31920
32235
  const footprintName = footprintResult.name + "_" + params.lcsc_id;
31921
32236
  footprintRef = getFootprintReference2(footprintName);
31922
32237
  await ensureDir(paths.footprintDir);
31923
- const footprintPath = join8(paths.footprintDir, `${footprintName}.kicad_mod`);
32238
+ const footprintPath = join9(paths.footprintDir, `${footprintName}.kicad_mod`);
31924
32239
  await writeText(footprintPath, footprintResult.content);
31925
32240
  }
31926
32241
  component.info.package = footprintRef;
31927
32242
  await ensureDir(paths.symbolsDir);
31928
32243
  let symbolContent;
31929
32244
  let symbolAction;
31930
- if (existsSync6(symbolFile)) {
32245
+ if (existsSync7(symbolFile)) {
31931
32246
  const existingContent = await readFile7(symbolFile, "utf-8");
31932
32247
  symbolContent = symbolConverter.replaceInLibrary(existingContent, component);
31933
32248
  symbolAction = "replaced";
@@ -31956,494 +32271,31 @@ async function handleFixLibrary(args) {
31956
32271
  };
31957
32272
  }
31958
32273
 
31959
- // src/tools/easyeda.ts
31960
- import { join as join9 } from "path";
31961
- import { execSync as execSync2 } from "child_process";
31962
- import { tmpdir } from "os";
31963
- var easyedaSearchTool = {
31964
- name: "easyeda_search",
31965
- description: "Search EasyEDA community library for user-contributed symbols and footprints. Use this for parts not in LCSC official library (e.g., XIAO, Arduino modules, custom breakouts). Returns results with UUIDs and optionally opens an HTML preview.",
31966
- inputSchema: {
31967
- type: "object",
31968
- properties: {
31969
- query: {
31970
- type: "string",
31971
- description: 'Search term (e.g., "XIAO RP2040", "ESP32-C3 module")'
31972
- },
31973
- source: {
31974
- type: "string",
31975
- enum: ["user", "lcsc", "easyeda", "all"],
31976
- description: 'Filter by source. "user" for community-contributed (default)'
31977
- },
31978
- limit: {
31979
- type: "number",
31980
- description: "Max results to return (default: 20)"
31981
- },
31982
- open_preview: {
31983
- type: "boolean",
31984
- description: "Generate and open HTML preview in browser",
31985
- default: true
31986
- }
31987
- },
31988
- required: ["query"]
31989
- }
31990
- };
31991
- var easyedaGet3DModelTool = {
31992
- name: "easyeda_get_3d_model",
31993
- description: "Download 3D model for an EasyEDA community component. Requires the model UUID from easyeda_get.",
31994
- inputSchema: {
31995
- type: "object",
31996
- properties: {
31997
- uuid: {
31998
- type: "string",
31999
- description: "3D model UUID from easyeda_get result"
32000
- },
32001
- format: {
32002
- type: "string",
32003
- enum: ["step", "obj"],
32004
- description: 'Model format: "step" or "obj" (default: step)'
32005
- }
32006
- },
32007
- required: ["uuid"]
32008
- }
32009
- };
32010
- var EasyedaSearchParamsSchema = exports_external.object({
32011
- query: exports_external.string().min(1),
32012
- source: exports_external.enum(["user", "lcsc", "easyeda", "all"]).optional(),
32013
- limit: exports_external.number().min(1).max(100).optional(),
32014
- open_preview: exports_external.boolean().optional()
32015
- });
32016
- var EasyedaGet3DModelParamsSchema = exports_external.object({
32017
- uuid: exports_external.string().min(1),
32018
- format: exports_external.enum(["step", "obj"]).default("step")
32019
- });
32020
- async function handleEasyedaSearch(args) {
32021
- const params = EasyedaSearchParamsSchema.parse(args);
32022
- const openPreview = params.open_preview ?? true;
32023
- const results = await easyedaCommunityClient.search({
32024
- query: params.query,
32025
- source: params.source,
32026
- limit: params.limit || 20
32027
- });
32028
- if (results.length === 0) {
32029
- return {
32030
- content: [
32031
- {
32032
- type: "text",
32033
- text: `No results found for "${params.query}"`
32034
- }
32035
- ]
32036
- };
32037
- }
32038
- let output = `Found ${results.length} results for "${params.query}":
32039
-
32040
- `;
32041
- output += `| # | Title | Package | Owner | UUID |
32042
- `;
32043
- output += `|---|-------|---------|-------|------|
32044
- `;
32045
- for (let i = 0;i < results.length; i++) {
32046
- const r = results[i];
32047
- output += `| ${i + 1} | ${r.title} | ${r.package} | ${r.owner.nickname || r.owner.username} | ${r.uuid} |
32048
- `;
32049
- }
32050
- output += "\nUse `library_fetch` with the UUID to add component to global JLC-MCP libraries.";
32051
- output += "\nUse `easyeda_fetch` with the UUID to add to project-local EasyEDA library.";
32052
- if (openPreview) {
32053
- const { filepath, browserOpened } = await generateHtmlPreview(params.query, results);
32054
- if (browserOpened) {
32055
- output += `
32056
-
32057
- HTML preview opened in browser.`;
32058
- } else {
32059
- output += `
32060
-
32061
- Could not open browser automatically.`;
32062
- }
32063
- output += `
32064
- Preview file: ${filepath}`;
32065
- }
32066
- return {
32067
- content: [
32068
- {
32069
- type: "text",
32070
- text: output
32071
- }
32072
- ]
32073
- };
32074
- }
32075
- async function handleEasyedaGet3DModel(args) {
32076
- const params = EasyedaGet3DModelParamsSchema.parse(args);
32077
- const model = await easyedaCommunityClient.get3DModel(params.uuid, params.format);
32078
- if (!model) {
32079
- return {
32080
- content: [
32081
- {
32082
- type: "text",
32083
- text: `3D model ${params.uuid} not found`
32084
- }
32085
- ],
32086
- isError: true
32087
- };
32088
- }
32089
- return {
32090
- content: [
32091
- {
32092
- type: "text",
32093
- text: `3D model downloaded (${model.length} bytes, ${params.format.toUpperCase()} format)
32094
-
32095
- Base64 data:
32096
- ${model.toString("base64").slice(0, 500)}...`
32097
- }
32098
- ]
32099
- };
32100
- }
32101
- async function generateHtmlPreview(query, results) {
32102
- const timestamp = Date.now();
32103
- const filename = `easyeda-search-${timestamp}.html`;
32104
- const filepath = join9(tmpdir(), filename);
32105
- const noImageSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 150" style="background:#2a2a2a"><text x="100" y="75" text-anchor="middle" fill="#666" font-size="12">No Preview</text></svg>`;
32106
- const noImageDataUri = `data:image/svg+xml,${encodeURIComponent(noImageSvg)}`;
32107
- const cardsPromises = results.slice(0, 10).map(async (r) => {
32108
- const symbolImageUrl = `https://image.easyeda.com/components/${r.uuid}.png`;
32109
- let footprintSvg = "";
32110
- try {
32111
- const component = await easyedaCommunityClient.getComponent(r.uuid);
32112
- if (component) {
32113
- const rawData = component.rawData;
32114
- const fpDataStr = rawData?.packageDetail?.dataStr || rawData?.dataStr;
32115
- if (fpDataStr?.shape) {
32116
- footprintSvg = generateFootprintSvg(fpDataStr);
32117
- }
32118
- }
32119
- } catch {}
32120
- const footprintDataUri = footprintSvg ? `data:image/svg+xml,${encodeURIComponent(footprintSvg)}` : noImageDataUri;
32121
- return `
32122
- <div class="card">
32123
- <div class="images">
32124
- <div class="image-box">
32125
- <div class="image-label">Symbol</div>
32126
- <img src="${symbolImageUrl}" alt="Symbol" onerror="this.src='${noImageDataUri}'">
32127
- </div>
32128
- <div class="image-box">
32129
- <div class="image-label">Footprint</div>
32130
- <img src="${footprintDataUri}" alt="Footprint">
32131
- </div>
32132
- </div>
32133
- <h3>${escapeHtml(r.title)}</h3>
32134
- <div class="meta">
32135
- <div><strong>Package:</strong> ${escapeHtml(r.package || "N/A")}</div>
32136
- <div><strong>Owner:</strong> ${escapeHtml(r.owner.nickname || r.owner.username)}</div>
32137
- ${r.manufacturer ? `<div><strong>Mfr:</strong> ${escapeHtml(r.manufacturer)}</div>` : ""}
32138
- </div>
32139
- <div class="uuid" onclick="navigator.clipboard.writeText('${r.uuid}'); this.classList.add('copied'); setTimeout(() => this.classList.remove('copied'), 1000);">
32140
- ${r.uuid}
32141
- </div>
32142
- </div>`;
32143
- });
32144
- const cards = (await Promise.all(cardsPromises)).join(`
32145
- `);
32146
- const html = `<!DOCTYPE html>
32147
- <html lang="en">
32148
- <head>
32149
- <meta charset="UTF-8">
32150
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
32151
- <title>EasyEDA Search: ${escapeHtml(query)}</title>
32152
- <style>
32153
- * { box-sizing: border-box; }
32154
- body {
32155
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
32156
- max-width: 1400px;
32157
- margin: 0 auto;
32158
- padding: 20px;
32159
- background: #f9f9f9;
32160
- color: #333;
32161
- }
32162
- h1 { margin-bottom: 8px; }
32163
- .subtitle { color: #666; margin-bottom: 20px; }
32164
- .grid {
32165
- display: grid;
32166
- grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
32167
- gap: 16px;
32168
- }
32169
- .card {
32170
- background: white;
32171
- border: 1px solid #ddd;
32172
- border-radius: 8px;
32173
- padding: 16px;
32174
- transition: box-shadow 0.2s;
32175
- }
32176
- .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
32177
- .card .images {
32178
- display: flex;
32179
- gap: 8px;
32180
- margin-bottom: 12px;
32181
- }
32182
- .card .image-box {
32183
- flex: 1;
32184
- min-width: 0;
32185
- }
32186
- .card .image-label {
32187
- font-size: 10px;
32188
- color: #888;
32189
- text-transform: uppercase;
32190
- text-align: center;
32191
- margin-bottom: 4px;
32192
- }
32193
- .card img {
32194
- width: 100%;
32195
- height: 120px;
32196
- object-fit: contain;
32197
- border-radius: 4px;
32198
- border: 1px solid #eee;
32199
- }
32200
- .card h3 {
32201
- margin: 0 0 8px;
32202
- font-size: 15px;
32203
- line-height: 1.3;
32204
- overflow: hidden;
32205
- text-overflow: ellipsis;
32206
- white-space: nowrap;
32207
- }
32208
- .card .meta {
32209
- color: #666;
32210
- font-size: 12px;
32211
- line-height: 1.6;
32212
- }
32213
- .card .meta div { margin-bottom: 2px; }
32214
- .card .uuid {
32215
- font-family: 'SF Mono', Monaco, 'Courier New', monospace;
32216
- font-size: 10px;
32217
- color: #888;
32218
- background: #f5f5f5;
32219
- padding: 6px 8px;
32220
- border-radius: 4px;
32221
- margin-top: 12px;
32222
- cursor: pointer;
32223
- word-break: break-all;
32224
- transition: background 0.2s;
32225
- }
32226
- .card .uuid:hover { background: #e8e8e8; }
32227
- .card .uuid.copied { background: #d4edda; color: #155724; }
32228
- .instructions {
32229
- background: #e8f4fd;
32230
- border: 1px solid #b8daff;
32231
- border-radius: 8px;
32232
- padding: 16px;
32233
- margin-bottom: 20px;
32234
- font-size: 14px;
32235
- }
32236
- .instructions code {
32237
- background: #fff;
32238
- padding: 2px 6px;
32239
- border-radius: 4px;
32240
- font-family: 'SF Mono', Monaco, monospace;
32241
- }
32242
- </style>
32243
- </head>
32244
- <body>
32245
- <h1>EasyEDA Search: "${escapeHtml(query)}"</h1>
32246
- <p class="subtitle">Found ${results.length} results. Click UUID to copy to clipboard.</p>
32247
-
32248
- <div class="instructions">
32249
- <strong>How to use:</strong><br>
32250
- 1. Click on a UUID to copy it<br>
32251
- 2. Use <code>library_fetch</code> with the UUID to add to global JLC-MCP libraries<br>
32252
- 3. Or use <code>easyeda_fetch</code> for project-local EasyEDA library
32253
- </div>
32254
-
32255
- <div class="grid">
32256
- ${cards}
32257
- </div>
32258
- </body>
32259
- </html>`;
32260
- __require("fs").writeFileSync(filepath, html, "utf-8");
32261
- const browserOpened = openInBrowser(filepath);
32262
- return { filepath, browserOpened };
32263
- }
32264
- function openInBrowser(filepath) {
32265
- const platform4 = process.platform;
32266
- try {
32267
- switch (platform4) {
32268
- case "darwin":
32269
- execSync2(`open "${filepath}"`, { stdio: "ignore" });
32270
- return true;
32271
- case "win32":
32272
- execSync2(`start "" "${filepath}"`, { stdio: "ignore", shell: "cmd.exe" });
32273
- return true;
32274
- case "linux":
32275
- default:
32276
- execSync2(`xdg-open "${filepath}"`, { stdio: "ignore" });
32277
- return true;
32278
- }
32279
- } catch {
32280
- const fallbacks = ["xdg-open", "sensible-browser", "x-www-browser", "gnome-open"];
32281
- for (const cmd of fallbacks) {
32282
- try {
32283
- execSync2(`${cmd} "${filepath}"`, { stdio: "ignore" });
32284
- return true;
32285
- } catch {}
32286
- }
32287
- return false;
32288
- }
32289
- }
32290
- function escapeHtml(str) {
32291
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
32292
- }
32293
- function generateFootprintSvg(dataStr) {
32294
- if (!dataStr.shape || dataStr.shape.length === 0) {
32295
- return "";
32296
- }
32297
- const bbox = dataStr.BBox || { x: 0, y: 0, width: 100, height: 100 };
32298
- const padding = 5;
32299
- const viewBox = `${bbox.x - padding} ${bbox.y - padding} ${bbox.width + padding * 2} ${bbox.height + padding * 2}`;
32300
- const regions = [];
32301
- const tracks = [];
32302
- const pads = [];
32303
- const holes = [];
32304
- const texts = [];
32305
- for (const shape of dataStr.shape) {
32306
- if (typeof shape !== "string")
32307
- continue;
32308
- if (shape.startsWith("SOLIDREGION~")) {
32309
- const svg = renderSolidRegion(shape);
32310
- if (svg)
32311
- regions.push(svg);
32312
- } else if (shape.startsWith("TRACK~")) {
32313
- const svg = renderTrackShape(shape);
32314
- if (svg)
32315
- tracks.push(svg);
32316
- } else if (shape.startsWith("PAD~")) {
32317
- const result = renderPadShape(shape);
32318
- if (result) {
32319
- pads.push(result.pad);
32320
- if (result.hole)
32321
- holes.push(result.hole);
32322
- }
32323
- } else if (shape.startsWith("TEXT~")) {
32324
- const svg = renderTextShape(shape);
32325
- if (svg)
32326
- texts.push(svg);
32327
- }
32328
- }
32329
- const allElements = [...regions, ...tracks, ...pads, ...holes, ...texts];
32330
- if (allElements.length === 0) {
32331
- return "";
32332
- }
32333
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" style="background:#000000">
32334
- <style>
32335
- .pad { fill: #CC0000; stroke: none; }
32336
- .pad-hole { fill: #666666; }
32337
- .track { fill: none; stroke: #FFFF00; stroke-linecap: round; stroke-linejoin: round; }
32338
- .region { fill: #CC0000; opacity: 0.6; }
32339
- .text-path { fill: none; stroke: #FFFF00; stroke-width: 0.4; stroke-linecap: round; stroke-linejoin: round; }
32340
- </style>
32341
- ${allElements.join(`
32342
- `)}
32343
- </svg>`;
32344
- }
32345
- function renderPadShape(padData) {
32346
- const fields = padData.split("~");
32347
- const shapeType = fields[1];
32348
- const cx = parseFloat(fields[2]) || 0;
32349
- const cy = parseFloat(fields[3]) || 0;
32350
- if (shapeType === "POLYGON") {
32351
- const holeDia2 = parseFloat(fields[9]) || 0;
32352
- const pointsStr = fields[10] || "";
32353
- if (!pointsStr)
32354
- return null;
32355
- const coords = pointsStr.split(" ").map(Number);
32356
- if (coords.length < 4)
32357
- return null;
32358
- let pathD = `M ${coords[0]} ${coords[1]}`;
32359
- for (let i = 2;i < coords.length; i += 2) {
32360
- pathD += ` L ${coords[i]} ${coords[i + 1]}`;
32361
- }
32362
- pathD += " Z";
32363
- return {
32364
- pad: `<path class="pad" d="${pathD}"/>`,
32365
- hole: holeDia2 > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia2}"/>` : null
32366
- };
32367
- }
32368
- const width = parseFloat(fields[4]) || 0;
32369
- const height = parseFloat(fields[5]) || 0;
32370
- const holeDia = parseFloat(fields[9]) || 0;
32371
- let padSvg = "";
32372
- if (shapeType === "ELLIPSE" || shapeType === "OVAL" || shapeType === "ROUND") {
32373
- const rx = width / 2;
32374
- const ry = height / 2;
32375
- padSvg = `<ellipse class="pad" cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}"/>`;
32376
- } else {
32377
- const rectX = cx - width / 2;
32378
- const rectY = cy - height / 2;
32379
- padSvg = `<rect class="pad" x="${rectX}" y="${rectY}" width="${width}" height="${height}"/>`;
32380
- }
32381
- return {
32382
- pad: padSvg,
32383
- hole: holeDia > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia}"/>` : null
32384
- };
32385
- }
32386
- function renderTrackShape(trackData) {
32387
- const fields = trackData.split("~");
32388
- const strokeWidth = parseFloat(fields[1]) || 0.5;
32389
- const pointsStr = fields[4] || "";
32390
- if (!pointsStr)
32391
- return null;
32392
- const coords = pointsStr.split(" ").map(Number);
32393
- if (coords.length < 4)
32394
- return null;
32395
- let pathD = `M ${coords[0]} ${coords[1]}`;
32396
- for (let i = 2;i < coords.length; i += 2) {
32397
- pathD += ` L ${coords[i]} ${coords[i + 1]}`;
32398
- }
32399
- return `<path class="track" d="${pathD}" stroke-width="${strokeWidth}"/>`;
32400
- }
32401
- function renderSolidRegion(regionData) {
32402
- const fields = regionData.split("~");
32403
- const pathD = fields[3] || "";
32404
- if (!pathD || !pathD.startsWith("M"))
32405
- return null;
32406
- return `<path class="region" d="${pathD}"/>`;
32407
- }
32408
- function renderTextShape(textData) {
32409
- const fields = textData.split("~");
32410
- const svgPath = fields[11] || "";
32411
- if (!svgPath || !svgPath.startsWith("M"))
32412
- return null;
32413
- return `<path class="text-path" d="${svgPath}"/>`;
32414
- }
32415
-
32416
32274
  // src/tools/index.ts
32417
32275
  var tools = [
32418
- searchComponentsTool,
32419
- getComponentTool,
32420
- getSymbolKicadTool,
32421
- getFootprintKicadTool,
32422
- fetchLibraryTool,
32276
+ componentSearchTool,
32277
+ libraryInstallTool,
32278
+ libraryBatchInstallTool,
32279
+ libraryGetComponentTool,
32423
32280
  updateLibraryTool,
32424
- fixLibraryTool,
32425
- get3DModelTool,
32426
- easyedaSearchTool,
32427
- easyedaGet3DModelTool
32281
+ fixLibraryTool
32428
32282
  ];
32429
32283
  var toolHandlers = {
32430
- component_search: handleSearchComponents,
32431
- component_get: handleGetComponent,
32432
- library_get_symbol: handleGetSymbolKicad,
32433
- library_get_footprint: handleGetFootprintKicad,
32434
- library_fetch: handleFetchLibrary,
32435
- library_update: handleUpdateLibrary,
32436
- library_fix: handleFixLibrary,
32437
- library_get_3d_model: handleGet3DModel,
32438
- easyeda_search: handleEasyedaSearch,
32439
- easyeda_get_3d_model: handleEasyedaGet3DModel
32284
+ [componentSearchTool.name]: handleComponentSearch,
32285
+ [libraryInstallTool.name]: handleLibraryInstall,
32286
+ [libraryBatchInstallTool.name]: handleLibraryBatchInstall,
32287
+ [libraryGetComponentTool.name]: handleLibraryGetComponent,
32288
+ [updateLibraryTool.name]: handleUpdateLibrary,
32289
+ [fixLibraryTool.name]: handleFixLibrary
32440
32290
  };
32441
32291
 
32442
32292
  // src/index.ts
32293
+ var require2 = createRequire(import.meta.url);
32294
+ var { version: version2 } = require2("../package.json");
32443
32295
  var logger2 = createLogger("jlc-mcp");
32444
32296
  var server = new Server({
32445
32297
  name: "jlc-mcp",
32446
- version: "0.1.0"
32298
+ version: version2
32447
32299
  }, {
32448
32300
  capabilities: {
32449
32301
  tools: {}
@@ -32484,9 +32336,9 @@ async function main() {
32484
32336
  });
32485
32337
  process.exit(1);
32486
32338
  }
32487
- const { symLibTable, fpLibTable, libraryStubs, version: version2 } = registration;
32339
+ const { symLibTable, fpLibTable, libraryStubs, version: version3 } = registration;
32488
32340
  if (symLibTable.created || symLibTable.modified || fpLibTable.created || fpLibTable.modified) {
32489
- logger2.info(`JLC libraries registered in KiCad ${version2}`, {
32341
+ logger2.info(`JLC libraries registered in KiCad ${version3}`, {
32490
32342
  symLibTable: symLibTable.created ? `created with ${symLibTable.entriesAdded} entries` : symLibTable.modified ? `added ${symLibTable.entriesAdded} entries` : "already configured",
32491
32343
  fpLibTable: fpLibTable.created ? "created" : fpLibTable.modified ? "updated" : "already configured",
32492
32344
  stubsCreated: libraryStubs.symbolsCreated.length + libraryStubs.directoriesCreated.length