@jlcpcb/mcp 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/index.js +503 -283
- package/package.json +2 -3
- package/src/index.ts +1 -2
- package/dist/assets/search.html +0 -528
- package/scripts/build-search-page.ts +0 -68
- package/src/assets/search-built.html +0 -528
- package/src/assets/search.html +0 -458
- package/src/browser/index.ts +0 -381
- package/src/browser/kicad-renderer.ts +0 -646
- package/src/browser/sexpr-parser.ts +0 -321
- package/src/http/routes.ts +0 -253
- package/src/http/server.ts +0 -74
package/dist/index.js
CHANGED
|
@@ -21903,6 +21903,10 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
21903
21903
|
import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
21904
21904
|
import { homedir as homedir2, platform as platform2 } from "os";
|
|
21905
21905
|
import { join as join4 } from "path";
|
|
21906
|
+
import { createServer } from "http";
|
|
21907
|
+
import { readFileSync } from "fs";
|
|
21908
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
21909
|
+
import { fileURLToPath } from "url";
|
|
21906
21910
|
var __defProp2 = Object.defineProperty;
|
|
21907
21911
|
var __export2 = (target, all) => {
|
|
21908
21912
|
for (var name in all)
|
|
@@ -22513,7 +22517,8 @@ var DEFAULT_LIBRARY_DESCRIPTION = "JLC-MCP Component Library (LCSC/EasyEDA)";
|
|
|
22513
22517
|
var init_lib_table = () => {};
|
|
22514
22518
|
var exports_global_lib_table = {};
|
|
22515
22519
|
__export2(exports_global_lib_table, {
|
|
22516
|
-
ensureGlobalLibraryTables: () => ensureGlobalLibraryTables
|
|
22520
|
+
ensureGlobalLibraryTables: () => ensureGlobalLibraryTables,
|
|
22521
|
+
ensureGlobalEasyEDALibrary: () => ensureGlobalEasyEDALibrary
|
|
22517
22522
|
});
|
|
22518
22523
|
function detectKicadVersion() {
|
|
22519
22524
|
const home = homedir();
|
|
@@ -22690,6 +22695,51 @@ async function ensureLibraryStubs(version2) {
|
|
|
22690
22695
|
}
|
|
22691
22696
|
return { symbolsCreated, directoriesCreated };
|
|
22692
22697
|
}
|
|
22698
|
+
function getEasyEDASymbolLibUri() {
|
|
22699
|
+
return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${EASYEDA_SYMBOL_LIBRARY_NAME}`;
|
|
22700
|
+
}
|
|
22701
|
+
function getEasyEDAFootprintLibUri() {
|
|
22702
|
+
return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/footprints/${EASYEDA_FOOTPRINT_LIBRARY_NAME}`;
|
|
22703
|
+
}
|
|
22704
|
+
async function ensureGlobalEasyEDALibrary() {
|
|
22705
|
+
const version2 = detectKicadVersion();
|
|
22706
|
+
const configDir = getKicadConfigDir(version2);
|
|
22707
|
+
await mkdir2(configDir, { recursive: true });
|
|
22708
|
+
const symTablePath = join3(configDir, "sym-lib-table");
|
|
22709
|
+
if (existsSync2(symTablePath)) {
|
|
22710
|
+
let content = await readFile3(symTablePath, "utf-8");
|
|
22711
|
+
if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
|
|
22712
|
+
const uri = getEasyEDASymbolLibUri();
|
|
22713
|
+
content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "sym", EASYEDA_LIBRARY_DESCRIPTION);
|
|
22714
|
+
await writeFile3(symTablePath, content, "utf-8");
|
|
22715
|
+
}
|
|
22716
|
+
} else {
|
|
22717
|
+
const uri = getEasyEDASymbolLibUri();
|
|
22718
|
+
const content = `(sym_lib_table
|
|
22719
|
+
(version 7)
|
|
22720
|
+
(lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
|
|
22721
|
+
)
|
|
22722
|
+
`;
|
|
22723
|
+
await writeFile3(symTablePath, content, "utf-8");
|
|
22724
|
+
}
|
|
22725
|
+
const fpTablePath = join3(configDir, "fp-lib-table");
|
|
22726
|
+
if (existsSync2(fpTablePath)) {
|
|
22727
|
+
let content = await readFile3(fpTablePath, "utf-8");
|
|
22728
|
+
if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
|
|
22729
|
+
const uri = getEasyEDAFootprintLibUri();
|
|
22730
|
+
content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "fp", EASYEDA_LIBRARY_DESCRIPTION);
|
|
22731
|
+
await writeFile3(fpTablePath, content, "utf-8");
|
|
22732
|
+
}
|
|
22733
|
+
} else {
|
|
22734
|
+
const uri = getEasyEDAFootprintLibUri();
|
|
22735
|
+
const content = `(fp_lib_table
|
|
22736
|
+
(version 7)
|
|
22737
|
+
(lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
|
|
22738
|
+
)
|
|
22739
|
+
`;
|
|
22740
|
+
await writeFile3(fpTablePath, content, "utf-8");
|
|
22741
|
+
}
|
|
22742
|
+
}
|
|
22693
22743
|
async function ensureGlobalLibraryTables() {
|
|
22694
22744
|
const errors22 = [];
|
|
22695
22745
|
const version2 = detectKicadVersion();
|
|
@@ -22742,6 +22792,10 @@ var LIBRARY_PREFIX2 = "JLC-MCP";
|
|
|
22742
22792
|
var LIBRARY_NAMESPACE = "jlc_mcp";
|
|
22743
22793
|
var KICAD_3RD_PARTY_VAR = "${KICAD9_3RD_PARTY}";
|
|
22744
22794
|
var LIBRARY_DESCRIPTION = "Autogenerated by JLC-MCP";
|
|
22795
|
+
var EASYEDA_LIBRARY_NAME = "EasyEDA";
|
|
22796
|
+
var EASYEDA_SYMBOL_LIBRARY_NAME = "EasyEDA.kicad_sym";
|
|
22797
|
+
var EASYEDA_FOOTPRINT_LIBRARY_NAME = "EasyEDA.pretty";
|
|
22798
|
+
var EASYEDA_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library";
|
|
22745
22799
|
var init_global_lib_table = __esm(() => {
|
|
22746
22800
|
init_category_router();
|
|
22747
22801
|
init_lib_table();
|
|
@@ -27478,7 +27532,7 @@ function parseFootprintShapes(shapes) {
|
|
|
27478
27532
|
}
|
|
27479
27533
|
}
|
|
27480
27534
|
}
|
|
27481
|
-
const type = pads.some((p) => p.holeRadius > 0) ? "tht" : "smd";
|
|
27535
|
+
const type = pads.some((p) => p.holeRadius > 0 || p.shape === "POLYGON" && p.isPlated) ? "tht" : "smd";
|
|
27482
27536
|
return {
|
|
27483
27537
|
name: "Unknown",
|
|
27484
27538
|
type,
|
|
@@ -29542,8 +29596,8 @@ var KI_PAD_LAYER_SMD = {
|
|
|
29542
29596
|
11: '"*.Cu" "*.Paste" "*.Mask"'
|
|
29543
29597
|
};
|
|
29544
29598
|
var KI_PAD_LAYER_THT = {
|
|
29545
|
-
1: '"
|
|
29546
|
-
2: '"
|
|
29599
|
+
1: '"*.Cu" "*.Mask"',
|
|
29600
|
+
2: '"*.Cu" "*.Mask"',
|
|
29547
29601
|
11: '"*.Cu" "*.Mask"'
|
|
29548
29602
|
};
|
|
29549
29603
|
var KI_PAD_SHAPE = {
|
|
@@ -29753,15 +29807,20 @@ class FootprintConverter {
|
|
|
29753
29807
|
`;
|
|
29754
29808
|
return output;
|
|
29755
29809
|
}
|
|
29756
|
-
generatePolygonPad(pad, origin,
|
|
29810
|
+
generatePolygonPad(pad, origin, _layers) {
|
|
29757
29811
|
const x = convertX(pad.centerX, origin.x);
|
|
29758
29812
|
const y = convertY(pad.centerY, origin.y);
|
|
29759
29813
|
const points = parsePoints(pad.points);
|
|
29760
29814
|
if (points.length < 3) {
|
|
29761
29815
|
return this.generatePad({ ...pad, shape: "RECT", points: "" }, origin);
|
|
29762
29816
|
}
|
|
29763
|
-
|
|
29817
|
+
let holeRadius = pad.holeRadius;
|
|
29818
|
+
if (holeRadius === 0 && pad.isPlated && pad.shape === "POLYGON") {
|
|
29819
|
+
holeRadius = this.calculateDrillRadiusFromPolygon(points, pad.centerX, pad.centerY);
|
|
29820
|
+
}
|
|
29821
|
+
const isSmd = holeRadius === 0;
|
|
29764
29822
|
const padType = isSmd ? "smd" : "thru_hole";
|
|
29823
|
+
const layers = getPadLayers(pad.layerId, isSmd);
|
|
29765
29824
|
const polyPoints = points.map((p) => ({
|
|
29766
29825
|
x: roundTo(toMM(p.x - pad.centerX), 2),
|
|
29767
29826
|
y: roundTo(toMM(p.y - pad.centerY), 2)
|
|
@@ -29773,7 +29832,7 @@ class FootprintConverter {
|
|
|
29773
29832
|
output += ` (size 0.01 0.01)
|
|
29774
29833
|
`;
|
|
29775
29834
|
if (!isSmd) {
|
|
29776
|
-
const drillDiameter = roundTo(toMM(
|
|
29835
|
+
const drillDiameter = roundTo(toMM(holeRadius * 2), 4);
|
|
29777
29836
|
if (pad.holeLength && pad.holeLength > 0) {
|
|
29778
29837
|
const holeH = roundTo(toMM(pad.holeLength), 4);
|
|
29779
29838
|
output += ` (drill oval ${drillDiameter} ${holeH})
|
|
@@ -29807,6 +29866,25 @@ class FootprintConverter {
|
|
|
29807
29866
|
`;
|
|
29808
29867
|
return output;
|
|
29809
29868
|
}
|
|
29869
|
+
calculateDrillRadiusFromPolygon(points, centerX, centerY) {
|
|
29870
|
+
if (points.length < 3)
|
|
29871
|
+
return 0;
|
|
29872
|
+
let minX = Infinity, maxX = -Infinity;
|
|
29873
|
+
let minY = Infinity, maxY = -Infinity;
|
|
29874
|
+
for (const pt of points) {
|
|
29875
|
+
const relX = pt.x - centerX;
|
|
29876
|
+
const relY = pt.y - centerY;
|
|
29877
|
+
minX = Math.min(minX, relX);
|
|
29878
|
+
maxX = Math.max(maxX, relX);
|
|
29879
|
+
minY = Math.min(minY, relY);
|
|
29880
|
+
maxY = Math.max(maxY, relY);
|
|
29881
|
+
}
|
|
29882
|
+
const width = maxX - minX;
|
|
29883
|
+
const height = maxY - minY;
|
|
29884
|
+
const minDim = Math.min(width, height);
|
|
29885
|
+
const drillDiameter = minDim * 0.6;
|
|
29886
|
+
return drillDiameter / 2;
|
|
29887
|
+
}
|
|
29810
29888
|
generateHole(hole, origin) {
|
|
29811
29889
|
const x = convertX(hole.centerX, origin.x);
|
|
29812
29890
|
const y = convertY(hole.centerY, origin.y);
|
|
@@ -29933,9 +30011,9 @@ class FootprintConverter {
|
|
|
29933
30011
|
const fontSize = roundTo(toMM(text.fontSize), 2);
|
|
29934
30012
|
const rotation = text.rotation || 0;
|
|
29935
30013
|
let justify = "";
|
|
29936
|
-
if (
|
|
30014
|
+
if (text.type === "L") {
|
|
29937
30015
|
justify = "left";
|
|
29938
|
-
} else if (
|
|
30016
|
+
} else if (text.type === "R") {
|
|
29939
30017
|
justify = "right";
|
|
29940
30018
|
}
|
|
29941
30019
|
return ` (fp_text user "${this.escapeString(text.text)}"
|
|
@@ -29952,6 +30030,9 @@ ${justify ? ` (justify ${justify})
|
|
|
29952
30030
|
`;
|
|
29953
30031
|
}
|
|
29954
30032
|
generateSolidRegion(region, origin) {
|
|
30033
|
+
if (region.layerId === 99 || region.layerId === 100 || region.layerId === 101) {
|
|
30034
|
+
return "";
|
|
30035
|
+
}
|
|
29955
30036
|
const layer = getLayer(region.layerId);
|
|
29956
30037
|
const points = this.parseSvgPathToPoints(region.path, origin);
|
|
29957
30038
|
if (points.length < 3)
|
|
@@ -29984,15 +30065,111 @@ ${justify ? ` (justify ${justify})
|
|
|
29984
30065
|
}
|
|
29985
30066
|
parseSvgPathToPoints(path, origin) {
|
|
29986
30067
|
const points = [];
|
|
29987
|
-
|
|
30068
|
+
let currentX = 0, currentY = 0;
|
|
30069
|
+
let startX = 0, startY = 0;
|
|
30070
|
+
const commandRegex = /([MLHVCSQTAZ])\s*([^MLHVCSQTAZ]*)/gi;
|
|
29988
30071
|
let match;
|
|
29989
30072
|
while ((match = commandRegex.exec(path)) !== null) {
|
|
29990
|
-
const
|
|
29991
|
-
const
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
30073
|
+
const cmd = match[1].toUpperCase();
|
|
30074
|
+
const args = match[2].trim().split(/[\s,]+/).map(parseFloat).filter((n) => !isNaN(n));
|
|
30075
|
+
switch (cmd) {
|
|
30076
|
+
case "M":
|
|
30077
|
+
if (args.length >= 2) {
|
|
30078
|
+
currentX = args[0];
|
|
30079
|
+
currentY = args[1];
|
|
30080
|
+
if (points.length === 0) {
|
|
30081
|
+
startX = currentX;
|
|
30082
|
+
startY = currentY;
|
|
30083
|
+
}
|
|
30084
|
+
points.push({
|
|
30085
|
+
x: convertX(currentX, origin.x),
|
|
30086
|
+
y: convertY(currentY, origin.y)
|
|
30087
|
+
});
|
|
30088
|
+
}
|
|
30089
|
+
break;
|
|
30090
|
+
case "L":
|
|
30091
|
+
if (args.length >= 2) {
|
|
30092
|
+
currentX = args[0];
|
|
30093
|
+
currentY = args[1];
|
|
30094
|
+
points.push({
|
|
30095
|
+
x: convertX(currentX, origin.x),
|
|
30096
|
+
y: convertY(currentY, origin.y)
|
|
30097
|
+
});
|
|
30098
|
+
}
|
|
30099
|
+
break;
|
|
30100
|
+
case "H":
|
|
30101
|
+
if (args.length >= 1) {
|
|
30102
|
+
currentX = args[0];
|
|
30103
|
+
points.push({
|
|
30104
|
+
x: convertX(currentX, origin.x),
|
|
30105
|
+
y: convertY(currentY, origin.y)
|
|
30106
|
+
});
|
|
30107
|
+
}
|
|
30108
|
+
break;
|
|
30109
|
+
case "V":
|
|
30110
|
+
if (args.length >= 1) {
|
|
30111
|
+
currentY = args[0];
|
|
30112
|
+
points.push({
|
|
30113
|
+
x: convertX(currentX, origin.x),
|
|
30114
|
+
y: convertY(currentY, origin.y)
|
|
30115
|
+
});
|
|
30116
|
+
}
|
|
30117
|
+
break;
|
|
30118
|
+
case "C":
|
|
30119
|
+
if (args.length >= 6) {
|
|
30120
|
+
currentX = args[4];
|
|
30121
|
+
currentY = args[5];
|
|
30122
|
+
points.push({
|
|
30123
|
+
x: convertX(currentX, origin.x),
|
|
30124
|
+
y: convertY(currentY, origin.y)
|
|
30125
|
+
});
|
|
30126
|
+
}
|
|
30127
|
+
break;
|
|
30128
|
+
case "S":
|
|
30129
|
+
if (args.length >= 4) {
|
|
30130
|
+
currentX = args[2];
|
|
30131
|
+
currentY = args[3];
|
|
30132
|
+
points.push({
|
|
30133
|
+
x: convertX(currentX, origin.x),
|
|
30134
|
+
y: convertY(currentY, origin.y)
|
|
30135
|
+
});
|
|
30136
|
+
}
|
|
30137
|
+
break;
|
|
30138
|
+
case "Q":
|
|
30139
|
+
if (args.length >= 4) {
|
|
30140
|
+
currentX = args[2];
|
|
30141
|
+
currentY = args[3];
|
|
30142
|
+
points.push({
|
|
30143
|
+
x: convertX(currentX, origin.x),
|
|
30144
|
+
y: convertY(currentY, origin.y)
|
|
30145
|
+
});
|
|
30146
|
+
}
|
|
30147
|
+
break;
|
|
30148
|
+
case "T":
|
|
30149
|
+
if (args.length >= 2) {
|
|
30150
|
+
currentX = args[0];
|
|
30151
|
+
currentY = args[1];
|
|
30152
|
+
points.push({
|
|
30153
|
+
x: convertX(currentX, origin.x),
|
|
30154
|
+
y: convertY(currentY, origin.y)
|
|
30155
|
+
});
|
|
30156
|
+
}
|
|
30157
|
+
break;
|
|
30158
|
+
case "A":
|
|
30159
|
+
if (args.length >= 7) {
|
|
30160
|
+
currentX = args[5];
|
|
30161
|
+
currentY = args[6];
|
|
30162
|
+
points.push({
|
|
30163
|
+
x: convertX(currentX, origin.x),
|
|
30164
|
+
y: convertY(currentY, origin.y)
|
|
30165
|
+
});
|
|
30166
|
+
}
|
|
30167
|
+
break;
|
|
30168
|
+
case "Z":
|
|
30169
|
+
currentX = startX;
|
|
30170
|
+
currentY = startY;
|
|
30171
|
+
break;
|
|
30172
|
+
}
|
|
29996
30173
|
}
|
|
29997
30174
|
return points;
|
|
29998
30175
|
}
|
|
@@ -30160,10 +30337,14 @@ var FOOTPRINT_LIBRARY_NAME = getFootprintDirName();
|
|
|
30160
30337
|
var MODELS_3D_NAME = get3DModelsDirName();
|
|
30161
30338
|
var LIBRARY_NAMESPACE2 = "jlc_mcp";
|
|
30162
30339
|
var KICAD_VERSIONS3 = ["9.0", "8.0"];
|
|
30163
|
-
var
|
|
30164
|
-
var
|
|
30165
|
-
var
|
|
30166
|
-
var
|
|
30340
|
+
var EASYEDA_LIBRARY_NAME2 = "EasyEDA";
|
|
30341
|
+
var EASYEDA_SYMBOL_LIBRARY_NAME2 = "EasyEDA.kicad_sym";
|
|
30342
|
+
var EASYEDA_FOOTPRINT_LIBRARY_NAME2 = "EasyEDA.pretty";
|
|
30343
|
+
var EASYEDA_LIBRARY_DESCRIPTION2 = "EasyEDA Community Component Library";
|
|
30344
|
+
var EASYEDA_LOCAL_LIBRARY_NAME = "EasyEDA-local";
|
|
30345
|
+
var EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME = "EasyEDA-local.kicad_sym";
|
|
30346
|
+
var EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME = "EasyEDA-local.pretty";
|
|
30347
|
+
var EASYEDA_LOCAL_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library (Project-local)";
|
|
30167
30348
|
function detectKicadVersion2() {
|
|
30168
30349
|
const home = homedir2();
|
|
30169
30350
|
const baseDir = join4(home, "Documents", "KiCad");
|
|
@@ -30304,10 +30485,7 @@ function createLibraryService() {
|
|
|
30304
30485
|
return {
|
|
30305
30486
|
async install(id, options = {}) {
|
|
30306
30487
|
const isCommunityComponent = !isLcscId2(id);
|
|
30307
|
-
|
|
30308
|
-
throw new Error("EasyEDA community components require projectPath for local storage");
|
|
30309
|
-
}
|
|
30310
|
-
const isGlobal = !isCommunityComponent && !options.projectPath;
|
|
30488
|
+
const isGlobal = !options.projectPath;
|
|
30311
30489
|
const paths = isGlobal ? getGlobalLibraryPaths() : getProjectLibraryPaths(options.projectPath);
|
|
30312
30490
|
let component = null;
|
|
30313
30491
|
if (isLcscId2(id)) {
|
|
@@ -30350,13 +30528,27 @@ function createLibraryService() {
|
|
|
30350
30528
|
let category;
|
|
30351
30529
|
let modelPath;
|
|
30352
30530
|
if (isCommunityComponent) {
|
|
30353
|
-
const
|
|
30354
|
-
const
|
|
30355
|
-
|
|
30356
|
-
|
|
30531
|
+
const libName = isGlobal ? EASYEDA_LIBRARY_NAME2 : EASYEDA_LOCAL_LIBRARY_NAME;
|
|
30532
|
+
const symLibFile = isGlobal ? EASYEDA_SYMBOL_LIBRARY_NAME2 : EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME;
|
|
30533
|
+
const fpLibDir = isGlobal ? EASYEDA_FOOTPRINT_LIBRARY_NAME2 : EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME;
|
|
30534
|
+
const libDesc = isGlobal ? EASYEDA_LIBRARY_DESCRIPTION2 : EASYEDA_LOCAL_LIBRARY_DESCRIPTION;
|
|
30535
|
+
const models3dDirName = isGlobal ? "EasyEDA.3dshapes" : "EasyEDA-local.3dshapes";
|
|
30536
|
+
let symbolsDir;
|
|
30537
|
+
let easyedaModelsDir;
|
|
30538
|
+
if (isGlobal) {
|
|
30539
|
+
symbolsDir = paths.symbolsDir;
|
|
30540
|
+
footprintDir = join4(paths.footprintsDir, fpLibDir);
|
|
30541
|
+
easyedaModelsDir = join4(paths.models3dDir, models3dDirName);
|
|
30542
|
+
} 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);
|
|
30547
|
+
}
|
|
30548
|
+
models3dDir = easyedaModelsDir;
|
|
30357
30549
|
await ensureDir(symbolsDir);
|
|
30358
30550
|
await ensureDir(footprintDir);
|
|
30359
|
-
symbolFile = join4(symbolsDir,
|
|
30551
|
+
symbolFile = join4(symbolsDir, symLibFile);
|
|
30360
30552
|
symbolName = component.info.name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30361
30553
|
const include3d = options.include3d ?? true;
|
|
30362
30554
|
let modelRelativePath;
|
|
@@ -30367,21 +30559,30 @@ function createLibraryService() {
|
|
|
30367
30559
|
const modelFilename = `${symbolName}.step`;
|
|
30368
30560
|
modelPath = join4(models3dDir, modelFilename);
|
|
30369
30561
|
await writeBinary(modelPath, model);
|
|
30370
|
-
|
|
30562
|
+
if (isGlobal) {
|
|
30563
|
+
modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${models3dDirName}/${modelFilename}`;
|
|
30564
|
+
} else {
|
|
30565
|
+
modelRelativePath = `\${KIPRJMOD}/libraries/3dmodels/${models3dDirName}/${modelFilename}`;
|
|
30566
|
+
}
|
|
30371
30567
|
}
|
|
30372
30568
|
}
|
|
30373
30569
|
const footprint = footprintConverter.convert(component, {
|
|
30374
|
-
libraryName:
|
|
30570
|
+
libraryName: libName,
|
|
30375
30571
|
include3DModel: !!modelRelativePath,
|
|
30376
30572
|
modelPath: modelRelativePath
|
|
30377
30573
|
});
|
|
30378
30574
|
footprintPath = join4(footprintDir, `${symbolName}.kicad_mod`);
|
|
30379
|
-
footprintRef = `${
|
|
30575
|
+
footprintRef = `${libName}:${symbolName}`;
|
|
30380
30576
|
await writeText(footprintPath, footprint);
|
|
30381
30577
|
component.info.package = footprintRef;
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30578
|
+
if (isGlobal) {
|
|
30579
|
+
const { ensureGlobalEasyEDALibrary: ensureGlobalEasyEDALibrary2 } = await Promise.resolve().then(() => (init_global_lib_table(), exports_global_lib_table));
|
|
30580
|
+
await ensureGlobalEasyEDALibrary2();
|
|
30581
|
+
} else {
|
|
30582
|
+
await ensureSymLibTable(options.projectPath, symbolFile, libName, libDesc);
|
|
30583
|
+
await ensureFpLibTable(options.projectPath, footprintDir, libName, libDesc);
|
|
30584
|
+
}
|
|
30585
|
+
symbolRef = `${libName}:${symbolName}`;
|
|
30385
30586
|
} else {
|
|
30386
30587
|
category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
|
|
30387
30588
|
const symbolLibraryFilename = getLibraryFilename(category);
|
|
@@ -30428,7 +30629,7 @@ function createLibraryService() {
|
|
|
30428
30629
|
if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
|
|
30429
30630
|
if (options.force) {
|
|
30430
30631
|
symbolContent = symbolConverter.replaceInLibrary(existingContent, component, {
|
|
30431
|
-
libraryName: isCommunityComponent ?
|
|
30632
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30432
30633
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30433
30634
|
});
|
|
30434
30635
|
symbolAction = "replaced";
|
|
@@ -30438,14 +30639,14 @@ function createLibraryService() {
|
|
|
30438
30639
|
}
|
|
30439
30640
|
} else {
|
|
30440
30641
|
symbolContent = symbolConverter.appendToLibrary(existingContent, component, {
|
|
30441
|
-
libraryName: isCommunityComponent ?
|
|
30642
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30442
30643
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30443
30644
|
});
|
|
30444
30645
|
symbolAction = "appended";
|
|
30445
30646
|
}
|
|
30446
30647
|
} else {
|
|
30447
30648
|
symbolContent = symbolConverter.convert(component, {
|
|
30448
|
-
libraryName: isCommunityComponent ?
|
|
30649
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30449
30650
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30450
30651
|
});
|
|
30451
30652
|
symbolAction = "created";
|
|
@@ -30570,12 +30771,236 @@ function createLibraryService() {
|
|
|
30570
30771
|
fpLibTable: fpLibTablePath
|
|
30571
30772
|
}
|
|
30572
30773
|
};
|
|
30774
|
+
},
|
|
30775
|
+
async isEasyEDAInstalled(componentName) {
|
|
30776
|
+
const paths = getGlobalLibraryPaths();
|
|
30777
|
+
const easyedaLibPath = join4(paths.symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME2);
|
|
30778
|
+
if (!existsSync3(easyedaLibPath)) {
|
|
30779
|
+
return false;
|
|
30780
|
+
}
|
|
30781
|
+
try {
|
|
30782
|
+
const content = await readFile4(easyedaLibPath, "utf-8");
|
|
30783
|
+
const sanitizedName = componentName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30784
|
+
const pattern = new RegExp(`\\(symbol\\s+"${sanitizedName}"`, "m");
|
|
30785
|
+
return pattern.test(content);
|
|
30786
|
+
} catch {
|
|
30787
|
+
return false;
|
|
30788
|
+
}
|
|
30573
30789
|
}
|
|
30574
30790
|
};
|
|
30575
30791
|
}
|
|
30576
30792
|
init_lib_table();
|
|
30577
30793
|
init_category_router();
|
|
30578
30794
|
init_global_lib_table();
|
|
30795
|
+
var logger7 = createLogger("http-routes");
|
|
30796
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
30797
|
+
var __dirname2 = dirname3(__filename2);
|
|
30798
|
+
var htmlCache = null;
|
|
30799
|
+
function getHtmlPage() {
|
|
30800
|
+
if (htmlCache)
|
|
30801
|
+
return htmlCache;
|
|
30802
|
+
try {
|
|
30803
|
+
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")
|
|
30814
|
+
];
|
|
30815
|
+
for (const path of possiblePaths) {
|
|
30816
|
+
try {
|
|
30817
|
+
htmlCache = readFileSync(path, "utf-8");
|
|
30818
|
+
logger7.debug(`Loaded HTML from: ${path}`);
|
|
30819
|
+
return htmlCache;
|
|
30820
|
+
} catch {}
|
|
30821
|
+
}
|
|
30822
|
+
throw new Error("HTML file not found");
|
|
30823
|
+
} catch (error2) {
|
|
30824
|
+
logger7.error("Failed to load HTML page:", error2);
|
|
30825
|
+
return `<!DOCTYPE html>
|
|
30826
|
+
<html>
|
|
30827
|
+
<head><title>Error</title></head>
|
|
30828
|
+
<body>
|
|
30829
|
+
<h1>Error: Search page not found</h1>
|
|
30830
|
+
<p>The search page has not been built. Run: bun run build</p>
|
|
30831
|
+
</body>
|
|
30832
|
+
</html>`;
|
|
30833
|
+
}
|
|
30834
|
+
}
|
|
30835
|
+
async function handleRequest(req, res) {
|
|
30836
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
30837
|
+
const pathname = url.pathname;
|
|
30838
|
+
logger7.debug(`${req.method} ${pathname}`);
|
|
30839
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
30840
|
+
const query = url.searchParams.get("q") || undefined;
|
|
30841
|
+
serveHtml(res, query);
|
|
30842
|
+
} else if (pathname === "/api/search") {
|
|
30843
|
+
await handleSearch(url, res);
|
|
30844
|
+
} else if (pathname.startsWith("/api/component/")) {
|
|
30845
|
+
const uuid2 = pathname.replace("/api/component/", "");
|
|
30846
|
+
await handleComponent(uuid2, res);
|
|
30847
|
+
} else if (pathname === "/health") {
|
|
30848
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
30849
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
30850
|
+
} else {
|
|
30851
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
30852
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
30853
|
+
}
|
|
30854
|
+
}
|
|
30855
|
+
function serveHtml(res, initialQuery) {
|
|
30856
|
+
let html = getHtmlPage();
|
|
30857
|
+
if (initialQuery) {
|
|
30858
|
+
const script = `<script>window.__INITIAL_QUERY__ = ${JSON.stringify(initialQuery)};</script>`;
|
|
30859
|
+
html = html.replace("</head>", `${script}</head>`);
|
|
30860
|
+
}
|
|
30861
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
30862
|
+
res.end(html);
|
|
30863
|
+
}
|
|
30864
|
+
async function handleSearch(url, res) {
|
|
30865
|
+
const query = url.searchParams.get("q") || "";
|
|
30866
|
+
const source = url.searchParams.get("source") || "user";
|
|
30867
|
+
const page = parseInt(url.searchParams.get("page") || "1", 10);
|
|
30868
|
+
const limit = parseInt(url.searchParams.get("limit") || "20", 10);
|
|
30869
|
+
if (!query) {
|
|
30870
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
30871
|
+
res.end(JSON.stringify({ error: "Missing query parameter" }));
|
|
30872
|
+
return;
|
|
30873
|
+
}
|
|
30874
|
+
try {
|
|
30875
|
+
const allResults = await easyedaCommunityClient.search({
|
|
30876
|
+
query,
|
|
30877
|
+
source,
|
|
30878
|
+
limit: Math.min(limit * page + limit, 100)
|
|
30879
|
+
});
|
|
30880
|
+
const startIndex = (page - 1) * limit;
|
|
30881
|
+
const endIndex = startIndex + limit;
|
|
30882
|
+
const results = allResults.slice(startIndex, endIndex);
|
|
30883
|
+
const totalPages = Math.ceil(allResults.length / limit);
|
|
30884
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
30885
|
+
res.end(JSON.stringify({
|
|
30886
|
+
results,
|
|
30887
|
+
pagination: {
|
|
30888
|
+
page,
|
|
30889
|
+
limit,
|
|
30890
|
+
total: allResults.length,
|
|
30891
|
+
totalPages,
|
|
30892
|
+
hasNext: page < totalPages,
|
|
30893
|
+
hasPrev: page > 1
|
|
30894
|
+
}
|
|
30895
|
+
}));
|
|
30896
|
+
} catch (error2) {
|
|
30897
|
+
logger7.error("Search error:", error2);
|
|
30898
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
30899
|
+
res.end(JSON.stringify({ error: "Search failed" }));
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
function communityToComponentData(community) {
|
|
30903
|
+
const cPara = community.symbol.head?.c_para ?? {};
|
|
30904
|
+
const fpCPara = community.footprint.head?.c_para ?? {};
|
|
30905
|
+
return {
|
|
30906
|
+
info: {
|
|
30907
|
+
name: community.title || cPara.name || "Unknown",
|
|
30908
|
+
prefix: cPara.pre || cPara.Prefix || "U",
|
|
30909
|
+
package: fpCPara.package || community.footprint.name,
|
|
30910
|
+
manufacturer: cPara.Manufacturer || cPara.BOM_Manufacturer,
|
|
30911
|
+
description: community.description || cPara.BOM_Manufacturer_Part,
|
|
30912
|
+
category: cPara.package
|
|
30913
|
+
},
|
|
30914
|
+
symbol: community.symbol,
|
|
30915
|
+
footprint: community.footprint,
|
|
30916
|
+
model3d: community.model3d,
|
|
30917
|
+
rawData: community.rawData
|
|
30918
|
+
};
|
|
30919
|
+
}
|
|
30920
|
+
async function handleComponent(uuid2, res) {
|
|
30921
|
+
if (!uuid2) {
|
|
30922
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
30923
|
+
res.end(JSON.stringify({ error: "Missing UUID" }));
|
|
30924
|
+
return;
|
|
30925
|
+
}
|
|
30926
|
+
try {
|
|
30927
|
+
const component2 = await easyedaCommunityClient.getComponent(uuid2);
|
|
30928
|
+
if (!component2) {
|
|
30929
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
30930
|
+
res.end(JSON.stringify({ error: "Component not found" }));
|
|
30931
|
+
return;
|
|
30932
|
+
}
|
|
30933
|
+
const componentData = communityToComponentData(component2);
|
|
30934
|
+
let symbolSexpr = "";
|
|
30935
|
+
let footprintSexpr = "";
|
|
30936
|
+
try {
|
|
30937
|
+
symbolSexpr = symbolConverter.convertToSymbolEntry(componentData);
|
|
30938
|
+
} catch (e) {
|
|
30939
|
+
logger7.warn("Symbol conversion failed:", e);
|
|
30940
|
+
}
|
|
30941
|
+
try {
|
|
30942
|
+
footprintSexpr = footprintConverter.convert(componentData);
|
|
30943
|
+
} catch (e) {
|
|
30944
|
+
logger7.warn("Footprint conversion failed:", e);
|
|
30945
|
+
}
|
|
30946
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
30947
|
+
res.end(JSON.stringify({
|
|
30948
|
+
uuid: component2.uuid,
|
|
30949
|
+
title: component2.title,
|
|
30950
|
+
description: component2.description,
|
|
30951
|
+
owner: component2.owner,
|
|
30952
|
+
symbolSexpr,
|
|
30953
|
+
footprintSexpr,
|
|
30954
|
+
model3d: component2.model3d
|
|
30955
|
+
}));
|
|
30956
|
+
} catch (error2) {
|
|
30957
|
+
logger7.error("Component fetch error:", error2);
|
|
30958
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
30959
|
+
res.end(JSON.stringify({ error: "Failed to fetch component" }));
|
|
30960
|
+
}
|
|
30961
|
+
}
|
|
30962
|
+
var logger8 = createLogger("http-server");
|
|
30963
|
+
var DEFAULT_PORT = 3847;
|
|
30964
|
+
var serverInstance = null;
|
|
30965
|
+
function startHttpServer(options = {}) {
|
|
30966
|
+
if (serverInstance) {
|
|
30967
|
+
logger8.debug("HTTP server already running");
|
|
30968
|
+
const port2 = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
|
|
30969
|
+
options.onReady?.(`http://localhost:${port2}`);
|
|
30970
|
+
return port2;
|
|
30971
|
+
}
|
|
30972
|
+
const port = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
|
|
30973
|
+
serverInstance = createServer(async (req, res) => {
|
|
30974
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
30975
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
30976
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
30977
|
+
if (req.method === "OPTIONS") {
|
|
30978
|
+
res.writeHead(204);
|
|
30979
|
+
res.end();
|
|
30980
|
+
return;
|
|
30981
|
+
}
|
|
30982
|
+
try {
|
|
30983
|
+
await handleRequest(req, res);
|
|
30984
|
+
} catch (error2) {
|
|
30985
|
+
logger8.error("Request error:", error2);
|
|
30986
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
30987
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
30988
|
+
}
|
|
30989
|
+
});
|
|
30990
|
+
serverInstance.listen(port, () => {
|
|
30991
|
+
const url = `http://localhost:${port}`;
|
|
30992
|
+
logger8.info(`HTTP server listening on ${url}`);
|
|
30993
|
+
options.onReady?.(url);
|
|
30994
|
+
});
|
|
30995
|
+
serverInstance.on("error", (error2) => {
|
|
30996
|
+
if (error2.code === "EADDRINUSE") {
|
|
30997
|
+
logger8.warn(`Port ${port} already in use, HTTP server not started`);
|
|
30998
|
+
} else {
|
|
30999
|
+
logger8.error("HTTP server error:", error2);
|
|
31000
|
+
}
|
|
31001
|
+
});
|
|
31002
|
+
return port;
|
|
31003
|
+
}
|
|
30579
31004
|
|
|
30580
31005
|
// src/tools/search.ts
|
|
30581
31006
|
var searchComponentsTool = {
|
|
@@ -30907,14 +31332,14 @@ ${model.toString("base64").slice(0, 500)}...`
|
|
|
30907
31332
|
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
30908
31333
|
import { readFile as readFile5 } from "fs/promises";
|
|
30909
31334
|
import { homedir as homedir3, platform as platform3 } from "os";
|
|
30910
|
-
import { join as
|
|
31335
|
+
import { join as join6 } from "path";
|
|
30911
31336
|
var KICAD_VERSIONS = ["9.0", "8.0"];
|
|
30912
31337
|
var LIBRARY_NAMESPACE3 = "jlc_mcp";
|
|
30913
31338
|
function detectKicadVersion3() {
|
|
30914
31339
|
const home = homedir3();
|
|
30915
|
-
const baseDir =
|
|
31340
|
+
const baseDir = join6(home, "Documents", "KiCad");
|
|
30916
31341
|
for (const version2 of KICAD_VERSIONS) {
|
|
30917
|
-
if (existsSync4(
|
|
31342
|
+
if (existsSync4(join6(baseDir, version2))) {
|
|
30918
31343
|
return version2;
|
|
30919
31344
|
}
|
|
30920
31345
|
}
|
|
@@ -30922,11 +31347,11 @@ function detectKicadVersion3() {
|
|
|
30922
31347
|
}
|
|
30923
31348
|
function getLibraryPaths(projectPath) {
|
|
30924
31349
|
if (projectPath) {
|
|
30925
|
-
const librariesDir =
|
|
31350
|
+
const librariesDir = join6(projectPath, "libraries");
|
|
30926
31351
|
return {
|
|
30927
|
-
symbolsDir:
|
|
30928
|
-
footprintDir:
|
|
30929
|
-
models3dDir:
|
|
31352
|
+
symbolsDir: join6(librariesDir, "symbols"),
|
|
31353
|
+
footprintDir: join6(librariesDir, "footprints", getFootprintDirName()),
|
|
31354
|
+
models3dDir: join6(librariesDir, "3dmodels", get3DModelsDirName())
|
|
30930
31355
|
};
|
|
30931
31356
|
}
|
|
30932
31357
|
const home = homedir3();
|
|
@@ -30934,14 +31359,14 @@ function getLibraryPaths(projectPath) {
|
|
|
30934
31359
|
const plat = platform3();
|
|
30935
31360
|
let base;
|
|
30936
31361
|
if (plat === "linux") {
|
|
30937
|
-
base =
|
|
31362
|
+
base = join6(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE3);
|
|
30938
31363
|
} else {
|
|
30939
|
-
base =
|
|
31364
|
+
base = join6(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE3);
|
|
30940
31365
|
}
|
|
30941
31366
|
return {
|
|
30942
|
-
symbolsDir:
|
|
30943
|
-
footprintDir:
|
|
30944
|
-
models3dDir:
|
|
31367
|
+
symbolsDir: join6(base, "symbols"),
|
|
31368
|
+
footprintDir: join6(base, "footprints", getFootprintDirName()),
|
|
31369
|
+
models3dDir: join6(base, "3dmodels", get3DModelsDirName())
|
|
30945
31370
|
};
|
|
30946
31371
|
}
|
|
30947
31372
|
function extractLcscIdsFromLibrary(content) {
|
|
@@ -30967,7 +31392,7 @@ function findJlcLibraries(symbolsDir) {
|
|
|
30967
31392
|
}
|
|
30968
31393
|
try {
|
|
30969
31394
|
const files = readdirSync(symbolsDir);
|
|
30970
|
-
return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) =>
|
|
31395
|
+
return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) => join6(symbolsDir, f));
|
|
30971
31396
|
} catch {
|
|
30972
31397
|
return [];
|
|
30973
31398
|
}
|
|
@@ -31034,7 +31459,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31034
31459
|
libraries_to_create: allCategories.map((cat) => ({
|
|
31035
31460
|
category: cat,
|
|
31036
31461
|
filename: getLibraryFilename(cat),
|
|
31037
|
-
path:
|
|
31462
|
+
path: join6(paths.symbolsDir, getLibraryFilename(cat))
|
|
31038
31463
|
}))
|
|
31039
31464
|
}, null, 2)
|
|
31040
31465
|
}]
|
|
@@ -31047,7 +31472,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31047
31472
|
const createdLibraries = [];
|
|
31048
31473
|
for (const category of allCategories) {
|
|
31049
31474
|
const filename = getLibraryFilename(category);
|
|
31050
|
-
const filepath =
|
|
31475
|
+
const filepath = join6(paths.symbolsDir, filename);
|
|
31051
31476
|
await writeText(filepath, emptyContent);
|
|
31052
31477
|
createdLibraries.push(filepath);
|
|
31053
31478
|
}
|
|
@@ -31124,7 +31549,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31124
31549
|
if (!params.dry_run && footprintResult.type === "generated") {
|
|
31125
31550
|
await ensureDir(paths.footprintDir);
|
|
31126
31551
|
const footprintName = footprintResult.name + "_" + lcscId;
|
|
31127
|
-
const footprintPath =
|
|
31552
|
+
const footprintPath = join6(paths.footprintDir, `${footprintName}.kicad_mod`);
|
|
31128
31553
|
await writeText(footprintPath, footprintResult.content);
|
|
31129
31554
|
}
|
|
31130
31555
|
results.push({
|
|
@@ -31151,7 +31576,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31151
31576
|
await ensureDir(paths.symbolsDir);
|
|
31152
31577
|
for (const [category, entries] of categorySymbols) {
|
|
31153
31578
|
const filename = getLibraryFilename(category);
|
|
31154
|
-
const filepath =
|
|
31579
|
+
const filepath = join6(paths.symbolsDir, filename);
|
|
31155
31580
|
const header = `(kicad_symbol_lib
|
|
31156
31581
|
(version 20241209)
|
|
31157
31582
|
(generator "jlc-mcp")
|
|
@@ -31186,7 +31611,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31186
31611
|
},
|
|
31187
31612
|
by_category: Object.fromEntries(byCategory),
|
|
31188
31613
|
footprint_stats: footprintStats,
|
|
31189
|
-
libraries_written: params.dry_run ? [] : Array.from(categorySymbols.keys()).map((cat) =>
|
|
31614
|
+
libraries_written: params.dry_run ? [] : Array.from(categorySymbols.keys()).map((cat) => join6(paths.symbolsDir, getLibraryFilename(cat))),
|
|
31190
31615
|
failed_components: failed.map((f) => ({
|
|
31191
31616
|
lcsc_id: f.lcscId,
|
|
31192
31617
|
error: f.error
|
|
@@ -31200,13 +31625,13 @@ async function handleUpdateLibrary(args) {
|
|
|
31200
31625
|
import { existsSync as existsSync6 } from "fs";
|
|
31201
31626
|
import { readFile as readFile7 } from "fs/promises";
|
|
31202
31627
|
import { homedir as homedir5 } from "os";
|
|
31203
|
-
import { join as
|
|
31628
|
+
import { join as join8 } from "path";
|
|
31204
31629
|
var KICAD_VERSIONS4 = ["9.0", "8.0"];
|
|
31205
31630
|
function detectKicadVersion5() {
|
|
31206
31631
|
const home = homedir5();
|
|
31207
|
-
const baseDir =
|
|
31632
|
+
const baseDir = join8(home, "Documents", "KiCad");
|
|
31208
31633
|
for (const version2 of KICAD_VERSIONS4) {
|
|
31209
|
-
if (existsSync6(
|
|
31634
|
+
if (existsSync6(join8(baseDir, version2))) {
|
|
31210
31635
|
return version2;
|
|
31211
31636
|
}
|
|
31212
31637
|
}
|
|
@@ -31214,18 +31639,18 @@ function detectKicadVersion5() {
|
|
|
31214
31639
|
}
|
|
31215
31640
|
function getLibraryPaths2(projectPath) {
|
|
31216
31641
|
if (projectPath) {
|
|
31217
|
-
const librariesDir =
|
|
31642
|
+
const librariesDir = join8(projectPath, "libraries");
|
|
31218
31643
|
return {
|
|
31219
|
-
symbolsDir:
|
|
31220
|
-
footprintDir:
|
|
31644
|
+
symbolsDir: join8(librariesDir, "symbols"),
|
|
31645
|
+
footprintDir: join8(librariesDir, "footprints", getFootprintDirName())
|
|
31221
31646
|
};
|
|
31222
31647
|
}
|
|
31223
31648
|
const home = homedir5();
|
|
31224
31649
|
const version2 = detectKicadVersion5();
|
|
31225
|
-
const base =
|
|
31650
|
+
const base = join8(home, "Documents", "KiCad", version2);
|
|
31226
31651
|
return {
|
|
31227
|
-
symbolsDir:
|
|
31228
|
-
footprintDir:
|
|
31652
|
+
symbolsDir: join8(base, "symbols"),
|
|
31653
|
+
footprintDir: join8(base, "footprints", getFootprintDirName())
|
|
31229
31654
|
};
|
|
31230
31655
|
}
|
|
31231
31656
|
var PinElectricalType = exports_external.enum([
|
|
@@ -31393,7 +31818,7 @@ async function handleFixLibrary(args) {
|
|
|
31393
31818
|
}
|
|
31394
31819
|
const category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
|
|
31395
31820
|
const symbolLibraryFilename = getLibraryFilename(category);
|
|
31396
|
-
const symbolFile =
|
|
31821
|
+
const symbolFile = join8(paths.symbolsDir, symbolLibraryFilename);
|
|
31397
31822
|
if (!existsSync6(symbolFile) && !params.force) {
|
|
31398
31823
|
return {
|
|
31399
31824
|
content: [{
|
|
@@ -31495,7 +31920,7 @@ async function handleFixLibrary(args) {
|
|
|
31495
31920
|
const footprintName = footprintResult.name + "_" + params.lcsc_id;
|
|
31496
31921
|
footprintRef = getFootprintReference2(footprintName);
|
|
31497
31922
|
await ensureDir(paths.footprintDir);
|
|
31498
|
-
const footprintPath =
|
|
31923
|
+
const footprintPath = join8(paths.footprintDir, `${footprintName}.kicad_mod`);
|
|
31499
31924
|
await writeText(footprintPath, footprintResult.content);
|
|
31500
31925
|
}
|
|
31501
31926
|
component.info.package = footprintRef;
|
|
@@ -31532,7 +31957,7 @@ async function handleFixLibrary(args) {
|
|
|
31532
31957
|
}
|
|
31533
31958
|
|
|
31534
31959
|
// src/tools/easyeda.ts
|
|
31535
|
-
import { join as
|
|
31960
|
+
import { join as join9 } from "path";
|
|
31536
31961
|
import { execSync as execSync2 } from "child_process";
|
|
31537
31962
|
import { tmpdir } from "os";
|
|
31538
31963
|
var easyedaSearchTool = {
|
|
@@ -31676,7 +32101,7 @@ ${model.toString("base64").slice(0, 500)}...`
|
|
|
31676
32101
|
async function generateHtmlPreview(query, results) {
|
|
31677
32102
|
const timestamp = Date.now();
|
|
31678
32103
|
const filename = `easyeda-search-${timestamp}.html`;
|
|
31679
|
-
const filepath =
|
|
32104
|
+
const filepath = join9(tmpdir(), filename);
|
|
31680
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>`;
|
|
31681
32106
|
const noImageDataUri = `data:image/svg+xml,${encodeURIComponent(noImageSvg)}`;
|
|
31682
32107
|
const cardsPromises = results.slice(0, 10).map(async (r) => {
|
|
@@ -32014,213 +32439,8 @@ var toolHandlers = {
|
|
|
32014
32439
|
easyeda_get_3d_model: handleEasyedaGet3DModel
|
|
32015
32440
|
};
|
|
32016
32441
|
|
|
32017
|
-
// src/http/server.ts
|
|
32018
|
-
import { createServer } from "http";
|
|
32019
|
-
|
|
32020
|
-
// src/http/routes.ts
|
|
32021
|
-
import { readFileSync } from "fs";
|
|
32022
|
-
import { join as join9, dirname as dirname2 } from "path";
|
|
32023
|
-
import { fileURLToPath } from "url";
|
|
32024
|
-
var logger2 = createLogger("http-routes");
|
|
32025
|
-
var __filename2 = fileURLToPath(import.meta.url);
|
|
32026
|
-
var __dirname2 = dirname2(__filename2);
|
|
32027
|
-
var htmlCache = null;
|
|
32028
|
-
function getHtmlPage() {
|
|
32029
|
-
if (htmlCache)
|
|
32030
|
-
return htmlCache;
|
|
32031
|
-
try {
|
|
32032
|
-
const possiblePaths = [
|
|
32033
|
-
join9(__dirname2, "assets/search.html"),
|
|
32034
|
-
join9(__dirname2, "../dist/assets/search.html"),
|
|
32035
|
-
join9(__dirname2, "../assets/search.html"),
|
|
32036
|
-
join9(__dirname2, "../assets/search-built.html"),
|
|
32037
|
-
join9(process.cwd(), "dist/assets/search.html"),
|
|
32038
|
-
join9(process.cwd(), "packages/jlc-mcp/dist/assets/search.html")
|
|
32039
|
-
];
|
|
32040
|
-
for (const path of possiblePaths) {
|
|
32041
|
-
try {
|
|
32042
|
-
htmlCache = readFileSync(path, "utf-8");
|
|
32043
|
-
logger2.debug(`Loaded HTML from: ${path}`);
|
|
32044
|
-
return htmlCache;
|
|
32045
|
-
} catch {}
|
|
32046
|
-
}
|
|
32047
|
-
throw new Error("HTML file not found");
|
|
32048
|
-
} catch (error2) {
|
|
32049
|
-
logger2.error("Failed to load HTML page:", error2);
|
|
32050
|
-
return `<!DOCTYPE html>
|
|
32051
|
-
<html>
|
|
32052
|
-
<head><title>Error</title></head>
|
|
32053
|
-
<body>
|
|
32054
|
-
<h1>Error: Search page not found</h1>
|
|
32055
|
-
<p>The search page has not been built. Run: bun run build</p>
|
|
32056
|
-
</body>
|
|
32057
|
-
</html>`;
|
|
32058
|
-
}
|
|
32059
|
-
}
|
|
32060
|
-
async function handleRequest(req, res) {
|
|
32061
|
-
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
32062
|
-
const pathname = url.pathname;
|
|
32063
|
-
logger2.debug(`${req.method} ${pathname}`);
|
|
32064
|
-
if (pathname === "/" || pathname === "/index.html") {
|
|
32065
|
-
serveHtml(res);
|
|
32066
|
-
} else if (pathname === "/api/search") {
|
|
32067
|
-
await handleSearch(url, res);
|
|
32068
|
-
} else if (pathname.startsWith("/api/component/")) {
|
|
32069
|
-
const uuid2 = pathname.replace("/api/component/", "");
|
|
32070
|
-
await handleComponent(uuid2, res);
|
|
32071
|
-
} else if (pathname === "/health") {
|
|
32072
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
32073
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
32074
|
-
} else {
|
|
32075
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
32076
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
32077
|
-
}
|
|
32078
|
-
}
|
|
32079
|
-
function serveHtml(res) {
|
|
32080
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
32081
|
-
res.end(getHtmlPage());
|
|
32082
|
-
}
|
|
32083
|
-
async function handleSearch(url, res) {
|
|
32084
|
-
const query = url.searchParams.get("q") || "";
|
|
32085
|
-
const source = url.searchParams.get("source") || "user";
|
|
32086
|
-
const page = parseInt(url.searchParams.get("page") || "1", 10);
|
|
32087
|
-
const limit = parseInt(url.searchParams.get("limit") || "20", 10);
|
|
32088
|
-
if (!query) {
|
|
32089
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
32090
|
-
res.end(JSON.stringify({ error: "Missing query parameter" }));
|
|
32091
|
-
return;
|
|
32092
|
-
}
|
|
32093
|
-
try {
|
|
32094
|
-
const allResults = await easyedaCommunityClient.search({
|
|
32095
|
-
query,
|
|
32096
|
-
source,
|
|
32097
|
-
limit: Math.min(limit * page + limit, 100)
|
|
32098
|
-
});
|
|
32099
|
-
const startIndex = (page - 1) * limit;
|
|
32100
|
-
const endIndex = startIndex + limit;
|
|
32101
|
-
const results = allResults.slice(startIndex, endIndex);
|
|
32102
|
-
const totalPages = Math.ceil(allResults.length / limit);
|
|
32103
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
32104
|
-
res.end(JSON.stringify({
|
|
32105
|
-
results,
|
|
32106
|
-
pagination: {
|
|
32107
|
-
page,
|
|
32108
|
-
limit,
|
|
32109
|
-
total: allResults.length,
|
|
32110
|
-
totalPages,
|
|
32111
|
-
hasNext: page < totalPages,
|
|
32112
|
-
hasPrev: page > 1
|
|
32113
|
-
}
|
|
32114
|
-
}));
|
|
32115
|
-
} catch (error2) {
|
|
32116
|
-
logger2.error("Search error:", error2);
|
|
32117
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
32118
|
-
res.end(JSON.stringify({ error: "Search failed" }));
|
|
32119
|
-
}
|
|
32120
|
-
}
|
|
32121
|
-
function communityToComponentData(community) {
|
|
32122
|
-
const cPara = community.symbol.head?.c_para ?? {};
|
|
32123
|
-
const fpCPara = community.footprint.head?.c_para ?? {};
|
|
32124
|
-
return {
|
|
32125
|
-
info: {
|
|
32126
|
-
name: community.title || cPara.name || "Unknown",
|
|
32127
|
-
prefix: cPara.pre || cPara.Prefix || "U",
|
|
32128
|
-
package: fpCPara.package || community.footprint.name,
|
|
32129
|
-
manufacturer: cPara.Manufacturer || cPara.BOM_Manufacturer,
|
|
32130
|
-
description: community.description || cPara.BOM_Manufacturer_Part,
|
|
32131
|
-
category: cPara.package
|
|
32132
|
-
},
|
|
32133
|
-
symbol: community.symbol,
|
|
32134
|
-
footprint: community.footprint,
|
|
32135
|
-
model3d: community.model3d,
|
|
32136
|
-
rawData: community.rawData
|
|
32137
|
-
};
|
|
32138
|
-
}
|
|
32139
|
-
async function handleComponent(uuid2, res) {
|
|
32140
|
-
if (!uuid2) {
|
|
32141
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
32142
|
-
res.end(JSON.stringify({ error: "Missing UUID" }));
|
|
32143
|
-
return;
|
|
32144
|
-
}
|
|
32145
|
-
try {
|
|
32146
|
-
const component = await easyedaCommunityClient.getComponent(uuid2);
|
|
32147
|
-
if (!component) {
|
|
32148
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
32149
|
-
res.end(JSON.stringify({ error: "Component not found" }));
|
|
32150
|
-
return;
|
|
32151
|
-
}
|
|
32152
|
-
const componentData = communityToComponentData(component);
|
|
32153
|
-
let symbolSexpr = "";
|
|
32154
|
-
let footprintSexpr = "";
|
|
32155
|
-
try {
|
|
32156
|
-
symbolSexpr = symbolConverter.convertToSymbolEntry(componentData);
|
|
32157
|
-
} catch (e) {
|
|
32158
|
-
logger2.warn("Symbol conversion failed:", e);
|
|
32159
|
-
}
|
|
32160
|
-
try {
|
|
32161
|
-
footprintSexpr = footprintConverter.convert(componentData);
|
|
32162
|
-
} catch (e) {
|
|
32163
|
-
logger2.warn("Footprint conversion failed:", e);
|
|
32164
|
-
}
|
|
32165
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
32166
|
-
res.end(JSON.stringify({
|
|
32167
|
-
uuid: component.uuid,
|
|
32168
|
-
title: component.title,
|
|
32169
|
-
description: component.description,
|
|
32170
|
-
owner: component.owner,
|
|
32171
|
-
symbolSexpr,
|
|
32172
|
-
footprintSexpr,
|
|
32173
|
-
model3d: component.model3d
|
|
32174
|
-
}));
|
|
32175
|
-
} catch (error2) {
|
|
32176
|
-
logger2.error("Component fetch error:", error2);
|
|
32177
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
32178
|
-
res.end(JSON.stringify({ error: "Failed to fetch component" }));
|
|
32179
|
-
}
|
|
32180
|
-
}
|
|
32181
|
-
|
|
32182
|
-
// src/http/server.ts
|
|
32183
|
-
var logger7 = createLogger("http-server");
|
|
32184
|
-
var DEFAULT_PORT = 3847;
|
|
32185
|
-
var serverInstance = null;
|
|
32186
|
-
function startHttpServer() {
|
|
32187
|
-
if (serverInstance) {
|
|
32188
|
-
logger7.debug("HTTP server already running");
|
|
32189
|
-
return DEFAULT_PORT;
|
|
32190
|
-
}
|
|
32191
|
-
const port = parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
|
|
32192
|
-
serverInstance = createServer(async (req, res) => {
|
|
32193
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
32194
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
32195
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
32196
|
-
if (req.method === "OPTIONS") {
|
|
32197
|
-
res.writeHead(204);
|
|
32198
|
-
res.end();
|
|
32199
|
-
return;
|
|
32200
|
-
}
|
|
32201
|
-
try {
|
|
32202
|
-
await handleRequest(req, res);
|
|
32203
|
-
} catch (error2) {
|
|
32204
|
-
logger7.error("Request error:", error2);
|
|
32205
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
32206
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
32207
|
-
}
|
|
32208
|
-
});
|
|
32209
|
-
serverInstance.listen(port, () => {
|
|
32210
|
-
logger7.info(`HTTP server listening on http://localhost:${port}`);
|
|
32211
|
-
});
|
|
32212
|
-
serverInstance.on("error", (error2) => {
|
|
32213
|
-
if (error2.code === "EADDRINUSE") {
|
|
32214
|
-
logger7.warn(`Port ${port} already in use, HTTP server not started`);
|
|
32215
|
-
} else {
|
|
32216
|
-
logger7.error("HTTP server error:", error2);
|
|
32217
|
-
}
|
|
32218
|
-
});
|
|
32219
|
-
return port;
|
|
32220
|
-
}
|
|
32221
|
-
|
|
32222
32442
|
// src/index.ts
|
|
32223
|
-
var
|
|
32443
|
+
var logger2 = createLogger("jlc-mcp");
|
|
32224
32444
|
var server = new Server({
|
|
32225
32445
|
name: "jlc-mcp",
|
|
32226
32446
|
version: "0.1.0"
|
|
@@ -32230,12 +32450,12 @@ var server = new Server({
|
|
|
32230
32450
|
}
|
|
32231
32451
|
});
|
|
32232
32452
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
32233
|
-
|
|
32453
|
+
logger2.debug("Listing tools");
|
|
32234
32454
|
return { tools };
|
|
32235
32455
|
});
|
|
32236
32456
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
32237
32457
|
const { name, arguments: args } = request.params;
|
|
32238
|
-
|
|
32458
|
+
logger2.debug(`Tool call: ${name}`, args);
|
|
32239
32459
|
const handler = toolHandlers[name];
|
|
32240
32460
|
if (!handler) {
|
|
32241
32461
|
return {
|
|
@@ -32246,7 +32466,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32246
32466
|
try {
|
|
32247
32467
|
return await handler(args);
|
|
32248
32468
|
} catch (error2) {
|
|
32249
|
-
|
|
32469
|
+
logger2.error(`Tool error: ${name}`, error2);
|
|
32250
32470
|
return {
|
|
32251
32471
|
content: [{
|
|
32252
32472
|
type: "text",
|
|
@@ -32259,26 +32479,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32259
32479
|
async function main() {
|
|
32260
32480
|
const registration = await ensureGlobalLibraryTables();
|
|
32261
32481
|
if (!registration.success) {
|
|
32262
|
-
|
|
32482
|
+
logger2.error("Failed to register JLC libraries in KiCad global tables", {
|
|
32263
32483
|
errors: registration.errors
|
|
32264
32484
|
});
|
|
32265
32485
|
process.exit(1);
|
|
32266
32486
|
}
|
|
32267
32487
|
const { symLibTable, fpLibTable, libraryStubs, version: version2 } = registration;
|
|
32268
32488
|
if (symLibTable.created || symLibTable.modified || fpLibTable.created || fpLibTable.modified) {
|
|
32269
|
-
|
|
32489
|
+
logger2.info(`JLC libraries registered in KiCad ${version2}`, {
|
|
32270
32490
|
symLibTable: symLibTable.created ? `created with ${symLibTable.entriesAdded} entries` : symLibTable.modified ? `added ${symLibTable.entriesAdded} entries` : "already configured",
|
|
32271
32491
|
fpLibTable: fpLibTable.created ? "created" : fpLibTable.modified ? "updated" : "already configured",
|
|
32272
32492
|
stubsCreated: libraryStubs.symbolsCreated.length + libraryStubs.directoriesCreated.length
|
|
32273
32493
|
});
|
|
32274
32494
|
}
|
|
32275
32495
|
const httpPort = startHttpServer();
|
|
32276
|
-
|
|
32496
|
+
logger2.info(`Component browser available at http://localhost:${httpPort}`);
|
|
32277
32497
|
const transport = new StdioServerTransport;
|
|
32278
32498
|
await server.connect(transport);
|
|
32279
|
-
|
|
32499
|
+
logger2.info("JLC MCP server running on stdio");
|
|
32280
32500
|
}
|
|
32281
32501
|
main().catch((error2) => {
|
|
32282
|
-
|
|
32502
|
+
logger2.error("Server error", error2);
|
|
32283
32503
|
process.exit(1);
|
|
32284
32504
|
});
|