@jlcpcb/mcp 0.1.1 → 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/CHANGELOG.md +72 -0
- package/README.md +19 -29
- package/dist/index.js +1271 -1199
- package/package.json +2 -3
- package/src/index.ts +6 -3
- package/src/schemas.ts +14 -0
- package/src/services.ts +34 -0
- package/src/tools/batch.ts +123 -0
- package/src/tools/index.ts +36 -63
- package/src/tools/library-fix.ts +1 -18
- package/src/tools/library-update.ts +8 -27
- package/src/tools/library.ts +108 -149
- package/src/tools/search.ts +68 -10
- 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/src/tools/details.ts +0 -66
- package/src/tools/easyeda.ts +0 -582
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,19 +21891,26 @@ 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 {
|
|
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
|
|
21903
|
-
import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
21904
|
-
import { homedir as
|
|
21905
|
-
import { join as
|
|
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";
|
|
21910
|
+
import { createServer } from "http";
|
|
21911
|
+
import { readFileSync } from "fs";
|
|
21912
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
21913
|
+
import { fileURLToPath } from "url";
|
|
21906
21914
|
var __defProp2 = Object.defineProperty;
|
|
21907
21915
|
var __export2 = (target, all) => {
|
|
21908
21916
|
for (var name in all)
|
|
@@ -21930,9 +21938,16 @@ function containsKeyword(text, keyword) {
|
|
|
21930
21938
|
return false;
|
|
21931
21939
|
}
|
|
21932
21940
|
function getLibraryCategory(prefix, category, description) {
|
|
21933
|
-
const
|
|
21934
|
-
if (
|
|
21935
|
-
|
|
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];
|
|
21936
21951
|
}
|
|
21937
21952
|
const searchText = [category || "", description || ""].join(" ");
|
|
21938
21953
|
if (searchText.trim()) {
|
|
@@ -21996,6 +22011,7 @@ var init_category_router = __esm(() => {
|
|
|
21996
22011
|
X: "Crystals",
|
|
21997
22012
|
J: "Connectors",
|
|
21998
22013
|
P: "Connectors",
|
|
22014
|
+
RJ: "Connectors",
|
|
21999
22015
|
K: "Misc",
|
|
22000
22016
|
F: "Misc"
|
|
22001
22017
|
};
|
|
@@ -22241,6 +22257,48 @@ var init_category_router = __esm(() => {
|
|
|
22241
22257
|
"ceramic resonator"
|
|
22242
22258
|
]
|
|
22243
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
|
+
},
|
|
22244
22302
|
{
|
|
22245
22303
|
category: "Transistors",
|
|
22246
22304
|
keywords: [
|
|
@@ -22341,46 +22399,6 @@ var init_category_router = __esm(() => {
|
|
|
22341
22399
|
"chip resistor"
|
|
22342
22400
|
]
|
|
22343
22401
|
},
|
|
22344
|
-
{
|
|
22345
|
-
category: "Connectors",
|
|
22346
|
-
keywords: [
|
|
22347
|
-
"connector",
|
|
22348
|
-
"header",
|
|
22349
|
-
"socket",
|
|
22350
|
-
"terminal",
|
|
22351
|
-
"terminal block",
|
|
22352
|
-
"jack",
|
|
22353
|
-
"plug",
|
|
22354
|
-
"receptacle",
|
|
22355
|
-
"usb-c",
|
|
22356
|
-
"usb type-c",
|
|
22357
|
-
"micro usb",
|
|
22358
|
-
"mini usb",
|
|
22359
|
-
"usb-a",
|
|
22360
|
-
"usb-b",
|
|
22361
|
-
"hdmi connector",
|
|
22362
|
-
"rj45",
|
|
22363
|
-
"rj11",
|
|
22364
|
-
"barrel jack",
|
|
22365
|
-
"dc jack",
|
|
22366
|
-
"audio jack",
|
|
22367
|
-
"jst",
|
|
22368
|
-
"jst-xh",
|
|
22369
|
-
"jst-ph",
|
|
22370
|
-
"molex",
|
|
22371
|
-
"dupont",
|
|
22372
|
-
"pin header",
|
|
22373
|
-
"female header",
|
|
22374
|
-
"fpc",
|
|
22375
|
-
"ffc",
|
|
22376
|
-
"sim card",
|
|
22377
|
-
"sd card",
|
|
22378
|
-
"microsd",
|
|
22379
|
-
"pogo pin",
|
|
22380
|
-
"spring contact",
|
|
22381
|
-
"test point"
|
|
22382
|
-
]
|
|
22383
|
-
},
|
|
22384
22402
|
{
|
|
22385
22403
|
category: "ICs",
|
|
22386
22404
|
keywords: [
|
|
@@ -22474,9 +22492,9 @@ function addLibraryToTable(tableContent, libraryName, libraryPath, type, descrip
|
|
|
22474
22492
|
`;
|
|
22475
22493
|
}
|
|
22476
22494
|
async function ensureSymLibTable(projectDir, symbolLibPath, libraryName = DEFAULT_LIBRARY_NAME, description = DEFAULT_LIBRARY_DESCRIPTION) {
|
|
22477
|
-
const tablePath =
|
|
22495
|
+
const tablePath = join3(projectDir, "sym-lib-table");
|
|
22478
22496
|
const relativePath = symbolLibPath.startsWith(projectDir) ? "${KIPRJMOD}" + symbolLibPath.slice(projectDir.length) : symbolLibPath;
|
|
22479
|
-
if (!
|
|
22497
|
+
if (!existsSync2(tablePath)) {
|
|
22480
22498
|
const content = generateSymLibTable(relativePath, libraryName, description);
|
|
22481
22499
|
await writeFile2(tablePath, content, "utf-8");
|
|
22482
22500
|
return { created: true, modified: false, path: tablePath };
|
|
@@ -22490,9 +22508,9 @@ async function ensureSymLibTable(projectDir, symbolLibPath, libraryName = DEFAUL
|
|
|
22490
22508
|
return { created: false, modified: true, path: tablePath };
|
|
22491
22509
|
}
|
|
22492
22510
|
async function ensureFpLibTable(projectDir, footprintLibPath, libraryName = DEFAULT_LIBRARY_NAME, description = DEFAULT_LIBRARY_DESCRIPTION) {
|
|
22493
|
-
const tablePath =
|
|
22511
|
+
const tablePath = join3(projectDir, "fp-lib-table");
|
|
22494
22512
|
const relativePath = footprintLibPath.startsWith(projectDir) ? "${KIPRJMOD}" + footprintLibPath.slice(projectDir.length) : footprintLibPath;
|
|
22495
|
-
if (!
|
|
22513
|
+
if (!existsSync2(tablePath)) {
|
|
22496
22514
|
const content = generateFpLibTable(relativePath, libraryName, description);
|
|
22497
22515
|
await writeFile2(tablePath, content, "utf-8");
|
|
22498
22516
|
return { created: true, modified: false, path: tablePath };
|
|
@@ -22513,39 +22531,40 @@ var DEFAULT_LIBRARY_DESCRIPTION = "JLC-MCP Component Library (LCSC/EasyEDA)";
|
|
|
22513
22531
|
var init_lib_table = () => {};
|
|
22514
22532
|
var exports_global_lib_table = {};
|
|
22515
22533
|
__export2(exports_global_lib_table, {
|
|
22516
|
-
ensureGlobalLibraryTables: () => ensureGlobalLibraryTables
|
|
22534
|
+
ensureGlobalLibraryTables: () => ensureGlobalLibraryTables,
|
|
22535
|
+
ensureGlobalEasyEDALibrary: () => ensureGlobalEasyEDALibrary
|
|
22517
22536
|
});
|
|
22518
|
-
function
|
|
22519
|
-
const home =
|
|
22520
|
-
const baseDir =
|
|
22537
|
+
function detectKicadVersion2() {
|
|
22538
|
+
const home = homedir2();
|
|
22539
|
+
const baseDir = join4(home, "Documents", "KiCad");
|
|
22521
22540
|
for (const version2 of KICAD_VERSIONS2) {
|
|
22522
|
-
if (
|
|
22541
|
+
if (existsSync3(join4(baseDir, version2))) {
|
|
22523
22542
|
return version2;
|
|
22524
22543
|
}
|
|
22525
22544
|
}
|
|
22526
22545
|
return "9.0";
|
|
22527
22546
|
}
|
|
22528
|
-
function
|
|
22529
|
-
const home =
|
|
22530
|
-
const plat =
|
|
22547
|
+
function getKicadConfigDir2(version2) {
|
|
22548
|
+
const home = homedir2();
|
|
22549
|
+
const plat = platform2();
|
|
22531
22550
|
if (plat === "darwin") {
|
|
22532
|
-
return
|
|
22551
|
+
return join4(home, "Library", "Preferences", "kicad", version2);
|
|
22533
22552
|
} else if (plat === "win32") {
|
|
22534
|
-
return
|
|
22553
|
+
return join4(process.env.APPDATA || join4(home, "AppData", "Roaming"), "kicad", version2);
|
|
22535
22554
|
} else {
|
|
22536
|
-
return
|
|
22555
|
+
return join4(home, ".config", "kicad", version2);
|
|
22537
22556
|
}
|
|
22538
22557
|
}
|
|
22539
22558
|
function get3rdPartyBaseDir(version2) {
|
|
22540
|
-
const home =
|
|
22541
|
-
const plat =
|
|
22559
|
+
const home = homedir2();
|
|
22560
|
+
const plat = platform2();
|
|
22542
22561
|
if (plat === "linux") {
|
|
22543
|
-
return
|
|
22562
|
+
return join4(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE);
|
|
22544
22563
|
}
|
|
22545
|
-
return
|
|
22564
|
+
return join4(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE);
|
|
22546
22565
|
}
|
|
22547
22566
|
function getSymbolLibPath(category, version2) {
|
|
22548
|
-
return
|
|
22567
|
+
return join4(get3rdPartyBaseDir(version2), "symbols", getLibraryFilename(category));
|
|
22549
22568
|
}
|
|
22550
22569
|
function getSymbolLibUri(category) {
|
|
22551
22570
|
return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${getLibraryFilename(category)}`;
|
|
@@ -22586,11 +22605,11 @@ function generateFpLibTable2() {
|
|
|
22586
22605
|
`;
|
|
22587
22606
|
}
|
|
22588
22607
|
async function ensureGlobalSymLibTable(version2) {
|
|
22589
|
-
const configDir =
|
|
22590
|
-
const tablePath =
|
|
22608
|
+
const configDir = getKicadConfigDir2(version2);
|
|
22609
|
+
const tablePath = join4(configDir, "sym-lib-table");
|
|
22591
22610
|
const categories = getAllCategories();
|
|
22592
22611
|
await mkdir2(configDir, { recursive: true });
|
|
22593
|
-
if (!
|
|
22612
|
+
if (!existsSync3(tablePath)) {
|
|
22594
22613
|
const content2 = generateSymLibTable2();
|
|
22595
22614
|
await writeFile3(tablePath, content2, "utf-8");
|
|
22596
22615
|
return {
|
|
@@ -22630,11 +22649,11 @@ async function ensureGlobalSymLibTable(version2) {
|
|
|
22630
22649
|
};
|
|
22631
22650
|
}
|
|
22632
22651
|
async function ensureGlobalFpLibTable(version2) {
|
|
22633
|
-
const configDir =
|
|
22634
|
-
const tablePath =
|
|
22652
|
+
const configDir = getKicadConfigDir2(version2);
|
|
22653
|
+
const tablePath = join4(configDir, "fp-lib-table");
|
|
22635
22654
|
const name = LIBRARY_PREFIX2;
|
|
22636
22655
|
await mkdir2(configDir, { recursive: true });
|
|
22637
|
-
if (!
|
|
22656
|
+
if (!existsSync3(tablePath)) {
|
|
22638
22657
|
const content2 = generateFpLibTable2();
|
|
22639
22658
|
await writeFile3(tablePath, content2, "utf-8");
|
|
22640
22659
|
return {
|
|
@@ -22668,13 +22687,13 @@ async function ensureGlobalFpLibTable(version2) {
|
|
|
22668
22687
|
}
|
|
22669
22688
|
async function ensureLibraryStubs(version2) {
|
|
22670
22689
|
const baseDir = get3rdPartyBaseDir(version2);
|
|
22671
|
-
const symbolsDir =
|
|
22672
|
-
const footprintsDir =
|
|
22673
|
-
const models3dDir =
|
|
22690
|
+
const symbolsDir = join4(baseDir, "symbols");
|
|
22691
|
+
const footprintsDir = join4(baseDir, "footprints", getFootprintDirName());
|
|
22692
|
+
const models3dDir = join4(baseDir, "3dmodels", get3DModelsDirName());
|
|
22674
22693
|
const directoriesCreated = [];
|
|
22675
22694
|
const symbolsCreated = [];
|
|
22676
22695
|
for (const dir of [symbolsDir, footprintsDir, models3dDir]) {
|
|
22677
|
-
if (!
|
|
22696
|
+
if (!existsSync3(dir)) {
|
|
22678
22697
|
await mkdir2(dir, { recursive: true });
|
|
22679
22698
|
directoriesCreated.push(dir);
|
|
22680
22699
|
}
|
|
@@ -22683,16 +22702,61 @@ async function ensureLibraryStubs(version2) {
|
|
|
22683
22702
|
const emptyContent = generateEmptySymbolLibrary();
|
|
22684
22703
|
for (const category of categories) {
|
|
22685
22704
|
const filePath = getSymbolLibPath(category, version2);
|
|
22686
|
-
if (!
|
|
22705
|
+
if (!existsSync3(filePath)) {
|
|
22687
22706
|
await writeFile3(filePath, emptyContent, "utf-8");
|
|
22688
22707
|
symbolsCreated.push(filePath);
|
|
22689
22708
|
}
|
|
22690
22709
|
}
|
|
22691
22710
|
return { symbolsCreated, directoriesCreated };
|
|
22692
22711
|
}
|
|
22712
|
+
function getEasyEDASymbolLibUri() {
|
|
22713
|
+
return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${EASYEDA_SYMBOL_LIBRARY_NAME}`;
|
|
22714
|
+
}
|
|
22715
|
+
function getEasyEDAFootprintLibUri() {
|
|
22716
|
+
return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/footprints/${EASYEDA_FOOTPRINT_LIBRARY_NAME}`;
|
|
22717
|
+
}
|
|
22718
|
+
async function ensureGlobalEasyEDALibrary() {
|
|
22719
|
+
const version2 = detectKicadVersion2();
|
|
22720
|
+
const configDir = getKicadConfigDir2(version2);
|
|
22721
|
+
await mkdir2(configDir, { recursive: true });
|
|
22722
|
+
const symTablePath = join4(configDir, "sym-lib-table");
|
|
22723
|
+
if (existsSync3(symTablePath)) {
|
|
22724
|
+
let content = await readFile3(symTablePath, "utf-8");
|
|
22725
|
+
if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
|
|
22726
|
+
const uri = getEasyEDASymbolLibUri();
|
|
22727
|
+
content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "sym", EASYEDA_LIBRARY_DESCRIPTION);
|
|
22728
|
+
await writeFile3(symTablePath, content, "utf-8");
|
|
22729
|
+
}
|
|
22730
|
+
} else {
|
|
22731
|
+
const uri = getEasyEDASymbolLibUri();
|
|
22732
|
+
const content = `(sym_lib_table
|
|
22733
|
+
(version 7)
|
|
22734
|
+
(lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
|
|
22735
|
+
)
|
|
22736
|
+
`;
|
|
22737
|
+
await writeFile3(symTablePath, content, "utf-8");
|
|
22738
|
+
}
|
|
22739
|
+
const fpTablePath = join4(configDir, "fp-lib-table");
|
|
22740
|
+
if (existsSync3(fpTablePath)) {
|
|
22741
|
+
let content = await readFile3(fpTablePath, "utf-8");
|
|
22742
|
+
if (!libraryExistsInTable(content, EASYEDA_LIBRARY_NAME)) {
|
|
22743
|
+
const uri = getEasyEDAFootprintLibUri();
|
|
22744
|
+
content = addLibraryToTable(content, EASYEDA_LIBRARY_NAME, uri, "fp", EASYEDA_LIBRARY_DESCRIPTION);
|
|
22745
|
+
await writeFile3(fpTablePath, content, "utf-8");
|
|
22746
|
+
}
|
|
22747
|
+
} else {
|
|
22748
|
+
const uri = getEasyEDAFootprintLibUri();
|
|
22749
|
+
const content = `(fp_lib_table
|
|
22750
|
+
(version 7)
|
|
22751
|
+
(lib (name "${EASYEDA_LIBRARY_NAME}")(type "KiCad")(uri "${uri}")(options "")(descr "${EASYEDA_LIBRARY_DESCRIPTION}"))
|
|
22752
|
+
)
|
|
22753
|
+
`;
|
|
22754
|
+
await writeFile3(fpTablePath, content, "utf-8");
|
|
22755
|
+
}
|
|
22756
|
+
}
|
|
22693
22757
|
async function ensureGlobalLibraryTables() {
|
|
22694
22758
|
const errors22 = [];
|
|
22695
|
-
const version2 =
|
|
22759
|
+
const version2 = detectKicadVersion2();
|
|
22696
22760
|
let symLibTable = {
|
|
22697
22761
|
path: "",
|
|
22698
22762
|
created: false,
|
|
@@ -22742,6 +22806,10 @@ var LIBRARY_PREFIX2 = "JLC-MCP";
|
|
|
22742
22806
|
var LIBRARY_NAMESPACE = "jlc_mcp";
|
|
22743
22807
|
var KICAD_3RD_PARTY_VAR = "${KICAD9_3RD_PARTY}";
|
|
22744
22808
|
var LIBRARY_DESCRIPTION = "Autogenerated by JLC-MCP";
|
|
22809
|
+
var EASYEDA_LIBRARY_NAME = "EasyEDA";
|
|
22810
|
+
var EASYEDA_SYMBOL_LIBRARY_NAME = "EasyEDA.kicad_sym";
|
|
22811
|
+
var EASYEDA_FOOTPRINT_LIBRARY_NAME = "EasyEDA.pretty";
|
|
22812
|
+
var EASYEDA_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library";
|
|
22745
22813
|
var init_global_lib_table = __esm(() => {
|
|
22746
22814
|
init_category_router();
|
|
22747
22815
|
init_lib_table();
|
|
@@ -26716,6 +26784,9 @@ var coerce2 = {
|
|
|
26716
26784
|
};
|
|
26717
26785
|
var NEVER3 = INVALID2;
|
|
26718
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");
|
|
26719
26790
|
var PackageSchema = exports_external2.string().min(1, "Package cannot be empty");
|
|
26720
26791
|
var PriceTierSchema = exports_external2.object({
|
|
26721
26792
|
quantity: exports_external2.number().positive(),
|
|
@@ -26791,6 +26862,9 @@ var KiCadPadShapeSchema = exports_external2.enum([
|
|
|
26791
26862
|
"roundrect",
|
|
26792
26863
|
"custom"
|
|
26793
26864
|
]);
|
|
26865
|
+
function isLcscId(id) {
|
|
26866
|
+
return LCSCPartNumberSchema.safeParse(id).success;
|
|
26867
|
+
}
|
|
26794
26868
|
async function ensureDir(path) {
|
|
26795
26869
|
await mkdir(path, { recursive: true });
|
|
26796
26870
|
}
|
|
@@ -26872,7 +26946,58 @@ var logger = new Logger({ prefix: "ai-eda" });
|
|
|
26872
26946
|
function createLogger(packageName) {
|
|
26873
26947
|
return logger.child(packageName);
|
|
26874
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
|
+
}
|
|
26875
26997
|
var logger3 = createLogger("jlc-api");
|
|
26998
|
+
function stripTemperaturePrefix(description) {
|
|
26999
|
+
return description.replace(/^-?\d+℃~[+-]?\d+℃\s*/, "");
|
|
27000
|
+
}
|
|
26876
27001
|
var JLCPCB_SEARCH_API = "https://jlcpcb.com/api/overseas-pcb-order/v1/shoppingCart/smtGood/selectSmtComponentList/v2";
|
|
26877
27002
|
|
|
26878
27003
|
class JLCClient {
|
|
@@ -26918,7 +27043,7 @@ class JLCClient {
|
|
|
26918
27043
|
package: c.componentSpecificationEn || "",
|
|
26919
27044
|
price: c.componentPrices?.[0]?.productPrice,
|
|
26920
27045
|
stock: c.stockCount,
|
|
26921
|
-
description: c.describe,
|
|
27046
|
+
description: stripTemperaturePrefix(c.describe),
|
|
26922
27047
|
productUrl: c.lcscGoodsUrl,
|
|
26923
27048
|
datasheetPdf: c.dataManualUrl,
|
|
26924
27049
|
category: c.componentTypeEn,
|
|
@@ -27158,6 +27283,25 @@ function parseSymbolPath(data) {
|
|
|
27158
27283
|
return null;
|
|
27159
27284
|
}
|
|
27160
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
|
+
}
|
|
27161
27305
|
function parsePad(data) {
|
|
27162
27306
|
try {
|
|
27163
27307
|
const f = data.split("~");
|
|
@@ -27325,6 +27469,7 @@ function parseSymbolShapes(shapes) {
|
|
|
27325
27469
|
const polylines = [];
|
|
27326
27470
|
const polygons = [];
|
|
27327
27471
|
const paths = [];
|
|
27472
|
+
const texts = [];
|
|
27328
27473
|
for (const line of shapes) {
|
|
27329
27474
|
if (typeof line !== "string")
|
|
27330
27475
|
continue;
|
|
@@ -27378,10 +27523,15 @@ function parseSymbolShapes(shapes) {
|
|
|
27378
27523
|
paths.push(path);
|
|
27379
27524
|
break;
|
|
27380
27525
|
}
|
|
27381
|
-
case "T":
|
|
27526
|
+
case "T": {
|
|
27527
|
+
const text = parseSymbolText(line);
|
|
27528
|
+
if (text && text.text.trim())
|
|
27529
|
+
texts.push(text);
|
|
27382
27530
|
break;
|
|
27531
|
+
}
|
|
27383
27532
|
}
|
|
27384
27533
|
}
|
|
27534
|
+
associatePinNamesFromTexts(pins, texts);
|
|
27385
27535
|
return {
|
|
27386
27536
|
pins,
|
|
27387
27537
|
rectangles,
|
|
@@ -27390,8 +27540,29 @@ function parseSymbolShapes(shapes) {
|
|
|
27390
27540
|
arcs,
|
|
27391
27541
|
polylines,
|
|
27392
27542
|
polygons,
|
|
27393
|
-
paths
|
|
27394
|
-
|
|
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
|
+
}
|
|
27395
27566
|
}
|
|
27396
27567
|
function parseFootprintShapes(shapes) {
|
|
27397
27568
|
const pads = [];
|
|
@@ -27478,7 +27649,7 @@ function parseFootprintShapes(shapes) {
|
|
|
27478
27649
|
}
|
|
27479
27650
|
}
|
|
27480
27651
|
}
|
|
27481
|
-
const type = pads.some((p) => p.holeRadius > 0) ? "tht" : "smd";
|
|
27652
|
+
const type = pads.some((p) => p.holeRadius > 0 || p.shape === "POLYGON" && p.isPlated) ? "tht" : "smd";
|
|
27482
27653
|
return {
|
|
27483
27654
|
name: "Unknown",
|
|
27484
27655
|
type,
|
|
@@ -27579,6 +27750,7 @@ class EasyEDAClient {
|
|
|
27579
27750
|
polylines: symbolData.polylines,
|
|
27580
27751
|
polygons: symbolData.polygons,
|
|
27581
27752
|
paths: symbolData.paths,
|
|
27753
|
+
texts: symbolData.texts,
|
|
27582
27754
|
origin: symbolOrigin
|
|
27583
27755
|
},
|
|
27584
27756
|
footprint: {
|
|
@@ -27752,6 +27924,7 @@ class EasyEDACommunityClient {
|
|
|
27752
27924
|
polylines: symbolData.polylines,
|
|
27753
27925
|
polygons: symbolData.polygons,
|
|
27754
27926
|
paths: symbolData.paths,
|
|
27927
|
+
texts: symbolData.texts,
|
|
27755
27928
|
origin: symbolOrigin,
|
|
27756
27929
|
head: dataStr.head || {}
|
|
27757
27930
|
},
|
|
@@ -27778,14 +27951,14 @@ class EasyEDACommunityClient {
|
|
|
27778
27951
|
}
|
|
27779
27952
|
}
|
|
27780
27953
|
var easyedaCommunityClient = new EasyEDACommunityClient;
|
|
27781
|
-
function
|
|
27954
|
+
function isLcscId2(id) {
|
|
27782
27955
|
return /^C\d+$/.test(id);
|
|
27783
27956
|
}
|
|
27784
27957
|
function createComponentService() {
|
|
27785
27958
|
return {
|
|
27786
27959
|
async search(query, options = {}) {
|
|
27787
27960
|
const { source = "lcsc", limit = 20, inStock, basicOnly } = options;
|
|
27788
|
-
if (source === "easyeda-community") {
|
|
27961
|
+
if (source === "community" || source === "easyeda-community") {
|
|
27789
27962
|
const results = await easyedaCommunityClient.search({
|
|
27790
27963
|
query,
|
|
27791
27964
|
limit
|
|
@@ -27802,7 +27975,7 @@ function createComponentService() {
|
|
|
27802
27975
|
return jlcClient.search(query, { limit, inStock, basicOnly });
|
|
27803
27976
|
},
|
|
27804
27977
|
async fetch(id) {
|
|
27805
|
-
if (
|
|
27978
|
+
if (isLcscId2(id)) {
|
|
27806
27979
|
return easyedaClient.getComponentData(id);
|
|
27807
27980
|
}
|
|
27808
27981
|
return null;
|
|
@@ -27830,43 +28003,6 @@ function createComponentService() {
|
|
|
27830
28003
|
}
|
|
27831
28004
|
};
|
|
27832
28005
|
}
|
|
27833
|
-
var KICAD_LAYERS = {
|
|
27834
|
-
F_CU: "F.Cu",
|
|
27835
|
-
B_CU: "B.Cu",
|
|
27836
|
-
IN1_CU: "In1.Cu",
|
|
27837
|
-
IN2_CU: "In2.Cu",
|
|
27838
|
-
IN3_CU: "In3.Cu",
|
|
27839
|
-
IN4_CU: "In4.Cu",
|
|
27840
|
-
F_ADHES: "F.Adhes",
|
|
27841
|
-
F_PASTE: "F.Paste",
|
|
27842
|
-
F_SILKS: "F.SilkS",
|
|
27843
|
-
F_MASK: "F.Mask",
|
|
27844
|
-
F_CRTYD: "F.CrtYd",
|
|
27845
|
-
F_FAB: "F.Fab",
|
|
27846
|
-
B_ADHES: "B.Adhes",
|
|
27847
|
-
B_PASTE: "B.Paste",
|
|
27848
|
-
B_SILKS: "B.SilkS",
|
|
27849
|
-
B_MASK: "B.Mask",
|
|
27850
|
-
B_CRTYD: "B.CrtYd",
|
|
27851
|
-
B_FAB: "B.Fab",
|
|
27852
|
-
EDGE_CUTS: "Edge.Cuts",
|
|
27853
|
-
MARGIN: "Margin",
|
|
27854
|
-
DWGS_USER: "Dwgs.User",
|
|
27855
|
-
CMTS_USER: "Cmts.User",
|
|
27856
|
-
ECO1_USER: "Eco1.User",
|
|
27857
|
-
ECO2_USER: "Eco2.User"
|
|
27858
|
-
};
|
|
27859
|
-
var KICAD_DEFAULTS = {
|
|
27860
|
-
TEXT_SIZE: 1.27,
|
|
27861
|
-
TEXT_THICKNESS: 0.15,
|
|
27862
|
-
WIRE_WIDTH: 0.25,
|
|
27863
|
-
GRID_SCHEMATIC: 2.54,
|
|
27864
|
-
GRID_PCB: 0.25,
|
|
27865
|
-
PIN_LENGTH: 1.27,
|
|
27866
|
-
PIN_NAME_OFFSET: 0
|
|
27867
|
-
};
|
|
27868
|
-
var KICAD_SYMBOL_VERSION = "20241209";
|
|
27869
|
-
var KICAD_FOOTPRINT_VERSION = "20241209";
|
|
27870
28006
|
var PATTERNS = {
|
|
27871
28007
|
resistor: /(\d+(?:\.\d+)?)\s*([kKmMgGrR]?)\s*(?:ohm|Ohm|OHM|Ω|R)?/i,
|
|
27872
28008
|
capacitor: /(\d+(?:\.\d+)?)\s*([pnuμmM]?)[Ff]?\s*(?:[\/\s]*(\d+)\s*[Vv])?/i,
|
|
@@ -28276,11 +28412,34 @@ function parseSvgArcPath(path) {
|
|
|
28276
28412
|
return null;
|
|
28277
28413
|
}
|
|
28278
28414
|
}
|
|
28279
|
-
|
|
28280
|
-
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
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
|
+
}
|
|
28438
|
+
var CATEGORY_TO_PREFIX = {
|
|
28439
|
+
Resistors: "R",
|
|
28440
|
+
Capacitors: "C",
|
|
28441
|
+
Inductors: "L",
|
|
28442
|
+
Diodes: "D"
|
|
28284
28443
|
};
|
|
28285
28444
|
var EE_TO_MM = 0.254;
|
|
28286
28445
|
var IC_PIN_LENGTH = 2.54;
|
|
@@ -28338,7 +28497,12 @@ class SymbolConverter {
|
|
|
28338
28497
|
return output;
|
|
28339
28498
|
}
|
|
28340
28499
|
hasRenderableShapes(symbol) {
|
|
28341
|
-
|
|
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;
|
|
28342
28506
|
}
|
|
28343
28507
|
generateFromShapes(component, name) {
|
|
28344
28508
|
const { info, symbol } = component;
|
|
@@ -28372,6 +28536,18 @@ class SymbolConverter {
|
|
|
28372
28536
|
if (pathOutput)
|
|
28373
28537
|
output += pathOutput;
|
|
28374
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
|
+
}
|
|
28375
28551
|
output += ` )
|
|
28376
28552
|
`;
|
|
28377
28553
|
output += ` (symbol "${name}_1_1"
|
|
@@ -28556,6 +28732,24 @@ class SymbolConverter {
|
|
|
28556
28732
|
`;
|
|
28557
28733
|
return output;
|
|
28558
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
|
+
}
|
|
28559
28753
|
parseSvgPath(pathStr, origin) {
|
|
28560
28754
|
const points = [];
|
|
28561
28755
|
let firstPoint = null;
|
|
@@ -29542,8 +29736,8 @@ var KI_PAD_LAYER_SMD = {
|
|
|
29542
29736
|
11: '"*.Cu" "*.Paste" "*.Mask"'
|
|
29543
29737
|
};
|
|
29544
29738
|
var KI_PAD_LAYER_THT = {
|
|
29545
|
-
1: '"
|
|
29546
|
-
2: '"
|
|
29739
|
+
1: '"*.Cu" "*.Mask"',
|
|
29740
|
+
2: '"*.Cu" "*.Mask"',
|
|
29547
29741
|
11: '"*.Cu" "*.Mask"'
|
|
29548
29742
|
};
|
|
29549
29743
|
var KI_PAD_SHAPE = {
|
|
@@ -29753,15 +29947,20 @@ class FootprintConverter {
|
|
|
29753
29947
|
`;
|
|
29754
29948
|
return output;
|
|
29755
29949
|
}
|
|
29756
|
-
generatePolygonPad(pad, origin,
|
|
29950
|
+
generatePolygonPad(pad, origin, _layers) {
|
|
29757
29951
|
const x = convertX(pad.centerX, origin.x);
|
|
29758
29952
|
const y = convertY(pad.centerY, origin.y);
|
|
29759
29953
|
const points = parsePoints(pad.points);
|
|
29760
29954
|
if (points.length < 3) {
|
|
29761
29955
|
return this.generatePad({ ...pad, shape: "RECT", points: "" }, origin);
|
|
29762
29956
|
}
|
|
29763
|
-
|
|
29957
|
+
let holeRadius = pad.holeRadius;
|
|
29958
|
+
if (holeRadius === 0 && pad.layerId === 11 && pad.shape === "POLYGON") {
|
|
29959
|
+
holeRadius = this.calculateDrillRadiusFromPolygon(points, pad.centerX, pad.centerY);
|
|
29960
|
+
}
|
|
29961
|
+
const isSmd = holeRadius === 0;
|
|
29764
29962
|
const padType = isSmd ? "smd" : "thru_hole";
|
|
29963
|
+
const layers = getPadLayers(pad.layerId, isSmd);
|
|
29765
29964
|
const polyPoints = points.map((p) => ({
|
|
29766
29965
|
x: roundTo(toMM(p.x - pad.centerX), 2),
|
|
29767
29966
|
y: roundTo(toMM(p.y - pad.centerY), 2)
|
|
@@ -29773,7 +29972,7 @@ class FootprintConverter {
|
|
|
29773
29972
|
output += ` (size 0.01 0.01)
|
|
29774
29973
|
`;
|
|
29775
29974
|
if (!isSmd) {
|
|
29776
|
-
const drillDiameter = roundTo(toMM(
|
|
29975
|
+
const drillDiameter = roundTo(toMM(holeRadius * 2), 4);
|
|
29777
29976
|
if (pad.holeLength && pad.holeLength > 0) {
|
|
29778
29977
|
const holeH = roundTo(toMM(pad.holeLength), 4);
|
|
29779
29978
|
output += ` (drill oval ${drillDiameter} ${holeH})
|
|
@@ -29807,6 +30006,25 @@ class FootprintConverter {
|
|
|
29807
30006
|
`;
|
|
29808
30007
|
return output;
|
|
29809
30008
|
}
|
|
30009
|
+
calculateDrillRadiusFromPolygon(points, centerX, centerY) {
|
|
30010
|
+
if (points.length < 3)
|
|
30011
|
+
return 0;
|
|
30012
|
+
let minX = Infinity, maxX = -Infinity;
|
|
30013
|
+
let minY = Infinity, maxY = -Infinity;
|
|
30014
|
+
for (const pt of points) {
|
|
30015
|
+
const relX = pt.x - centerX;
|
|
30016
|
+
const relY = pt.y - centerY;
|
|
30017
|
+
minX = Math.min(minX, relX);
|
|
30018
|
+
maxX = Math.max(maxX, relX);
|
|
30019
|
+
minY = Math.min(minY, relY);
|
|
30020
|
+
maxY = Math.max(maxY, relY);
|
|
30021
|
+
}
|
|
30022
|
+
const width = maxX - minX;
|
|
30023
|
+
const height = maxY - minY;
|
|
30024
|
+
const minDim = Math.min(width, height);
|
|
30025
|
+
const drillDiameter = minDim * 0.6;
|
|
30026
|
+
return drillDiameter / 2;
|
|
30027
|
+
}
|
|
29810
30028
|
generateHole(hole, origin) {
|
|
29811
30029
|
const x = convertX(hole.centerX, origin.x);
|
|
29812
30030
|
const y = convertY(hole.centerY, origin.y);
|
|
@@ -29933,9 +30151,9 @@ class FootprintConverter {
|
|
|
29933
30151
|
const fontSize = roundTo(toMM(text.fontSize), 2);
|
|
29934
30152
|
const rotation = text.rotation || 0;
|
|
29935
30153
|
let justify = "";
|
|
29936
|
-
if (
|
|
30154
|
+
if (text.type === "L") {
|
|
29937
30155
|
justify = "left";
|
|
29938
|
-
} else if (
|
|
30156
|
+
} else if (text.type === "R") {
|
|
29939
30157
|
justify = "right";
|
|
29940
30158
|
}
|
|
29941
30159
|
return ` (fp_text user "${this.escapeString(text.text)}"
|
|
@@ -29952,6 +30170,9 @@ ${justify ? ` (justify ${justify})
|
|
|
29952
30170
|
`;
|
|
29953
30171
|
}
|
|
29954
30172
|
generateSolidRegion(region, origin) {
|
|
30173
|
+
if (region.layerId === 99 || region.layerId === 100 || region.layerId === 101) {
|
|
30174
|
+
return "";
|
|
30175
|
+
}
|
|
29955
30176
|
const layer = getLayer(region.layerId);
|
|
29956
30177
|
const points = this.parseSvgPathToPoints(region.path, origin);
|
|
29957
30178
|
if (points.length < 3)
|
|
@@ -29984,15 +30205,125 @@ ${justify ? ` (justify ${justify})
|
|
|
29984
30205
|
}
|
|
29985
30206
|
parseSvgPathToPoints(path, origin) {
|
|
29986
30207
|
const points = [];
|
|
29987
|
-
|
|
30208
|
+
let currentX = 0, currentY = 0;
|
|
30209
|
+
let startX = 0, startY = 0;
|
|
30210
|
+
const commandRegex = /([MLHVCSQTAZ])\s*([^MLHVCSQTAZ]*)/gi;
|
|
29988
30211
|
let match;
|
|
29989
30212
|
while ((match = commandRegex.exec(path)) !== null) {
|
|
29990
|
-
const
|
|
29991
|
-
const
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
30213
|
+
const cmd = match[1].toUpperCase();
|
|
30214
|
+
const args = match[2].trim().split(/[\s,]+/).map(parseFloat).filter((n) => !isNaN(n));
|
|
30215
|
+
switch (cmd) {
|
|
30216
|
+
case "M":
|
|
30217
|
+
if (args.length >= 2) {
|
|
30218
|
+
currentX = args[0];
|
|
30219
|
+
currentY = args[1];
|
|
30220
|
+
if (points.length === 0) {
|
|
30221
|
+
startX = currentX;
|
|
30222
|
+
startY = currentY;
|
|
30223
|
+
}
|
|
30224
|
+
points.push({
|
|
30225
|
+
x: convertX(currentX, origin.x),
|
|
30226
|
+
y: convertY(currentY, origin.y)
|
|
30227
|
+
});
|
|
30228
|
+
}
|
|
30229
|
+
break;
|
|
30230
|
+
case "L":
|
|
30231
|
+
if (args.length >= 2) {
|
|
30232
|
+
currentX = args[0];
|
|
30233
|
+
currentY = args[1];
|
|
30234
|
+
points.push({
|
|
30235
|
+
x: convertX(currentX, origin.x),
|
|
30236
|
+
y: convertY(currentY, origin.y)
|
|
30237
|
+
});
|
|
30238
|
+
}
|
|
30239
|
+
break;
|
|
30240
|
+
case "H":
|
|
30241
|
+
if (args.length >= 1) {
|
|
30242
|
+
currentX = args[0];
|
|
30243
|
+
points.push({
|
|
30244
|
+
x: convertX(currentX, origin.x),
|
|
30245
|
+
y: convertY(currentY, origin.y)
|
|
30246
|
+
});
|
|
30247
|
+
}
|
|
30248
|
+
break;
|
|
30249
|
+
case "V":
|
|
30250
|
+
if (args.length >= 1) {
|
|
30251
|
+
currentY = args[0];
|
|
30252
|
+
points.push({
|
|
30253
|
+
x: convertX(currentX, origin.x),
|
|
30254
|
+
y: convertY(currentY, origin.y)
|
|
30255
|
+
});
|
|
30256
|
+
}
|
|
30257
|
+
break;
|
|
30258
|
+
case "C":
|
|
30259
|
+
if (args.length >= 6) {
|
|
30260
|
+
currentX = args[4];
|
|
30261
|
+
currentY = args[5];
|
|
30262
|
+
points.push({
|
|
30263
|
+
x: convertX(currentX, origin.x),
|
|
30264
|
+
y: convertY(currentY, origin.y)
|
|
30265
|
+
});
|
|
30266
|
+
}
|
|
30267
|
+
break;
|
|
30268
|
+
case "S":
|
|
30269
|
+
if (args.length >= 4) {
|
|
30270
|
+
currentX = args[2];
|
|
30271
|
+
currentY = args[3];
|
|
30272
|
+
points.push({
|
|
30273
|
+
x: convertX(currentX, origin.x),
|
|
30274
|
+
y: convertY(currentY, origin.y)
|
|
30275
|
+
});
|
|
30276
|
+
}
|
|
30277
|
+
break;
|
|
30278
|
+
case "Q":
|
|
30279
|
+
if (args.length >= 4) {
|
|
30280
|
+
currentX = args[2];
|
|
30281
|
+
currentY = args[3];
|
|
30282
|
+
points.push({
|
|
30283
|
+
x: convertX(currentX, origin.x),
|
|
30284
|
+
y: convertY(currentY, origin.y)
|
|
30285
|
+
});
|
|
30286
|
+
}
|
|
30287
|
+
break;
|
|
30288
|
+
case "T":
|
|
30289
|
+
if (args.length >= 2) {
|
|
30290
|
+
currentX = args[0];
|
|
30291
|
+
currentY = args[1];
|
|
30292
|
+
points.push({
|
|
30293
|
+
x: convertX(currentX, origin.x),
|
|
30294
|
+
y: convertY(currentY, origin.y)
|
|
30295
|
+
});
|
|
30296
|
+
}
|
|
30297
|
+
break;
|
|
30298
|
+
case "A":
|
|
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
|
+
}
|
|
30318
|
+
currentX = args[5];
|
|
30319
|
+
currentY = args[6];
|
|
30320
|
+
}
|
|
30321
|
+
break;
|
|
30322
|
+
case "Z":
|
|
30323
|
+
currentX = startX;
|
|
30324
|
+
currentY = startY;
|
|
30325
|
+
break;
|
|
30326
|
+
}
|
|
29996
30327
|
}
|
|
29997
30328
|
return points;
|
|
29998
30329
|
}
|
|
@@ -30159,63 +30490,56 @@ init_category_router();
|
|
|
30159
30490
|
var FOOTPRINT_LIBRARY_NAME = getFootprintDirName();
|
|
30160
30491
|
var MODELS_3D_NAME = get3DModelsDirName();
|
|
30161
30492
|
var LIBRARY_NAMESPACE2 = "jlc_mcp";
|
|
30162
|
-
var
|
|
30163
|
-
var
|
|
30164
|
-
var
|
|
30165
|
-
var
|
|
30166
|
-
var
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
30170
|
-
for (const version2 of KICAD_VERSIONS3) {
|
|
30171
|
-
if (existsSync3(join4(baseDir, version2))) {
|
|
30172
|
-
return version2;
|
|
30173
|
-
}
|
|
30174
|
-
}
|
|
30175
|
-
return "9.0";
|
|
30176
|
-
}
|
|
30493
|
+
var EASYEDA_LIBRARY_NAME2 = "EasyEDA";
|
|
30494
|
+
var EASYEDA_SYMBOL_LIBRARY_NAME2 = "EasyEDA.kicad_sym";
|
|
30495
|
+
var EASYEDA_FOOTPRINT_LIBRARY_NAME2 = "EasyEDA.pretty";
|
|
30496
|
+
var EASYEDA_LIBRARY_DESCRIPTION2 = "EasyEDA Community Component Library";
|
|
30497
|
+
var EASYEDA_LOCAL_LIBRARY_NAME = "EasyEDA-local";
|
|
30498
|
+
var EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME = "EasyEDA-local.kicad_sym";
|
|
30499
|
+
var EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME = "EasyEDA-local.pretty";
|
|
30500
|
+
var EASYEDA_LOCAL_LIBRARY_DESCRIPTION = "EasyEDA Community Component Library (Project-local)";
|
|
30177
30501
|
function getGlobalLibraryPaths() {
|
|
30178
|
-
const home =
|
|
30179
|
-
const version2 =
|
|
30180
|
-
const plat =
|
|
30502
|
+
const home = homedir3();
|
|
30503
|
+
const version2 = detectKicadVersion();
|
|
30504
|
+
const plat = platform3();
|
|
30181
30505
|
let base;
|
|
30182
30506
|
if (plat === "linux") {
|
|
30183
|
-
base =
|
|
30507
|
+
base = join5(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE2);
|
|
30184
30508
|
} else {
|
|
30185
|
-
base =
|
|
30509
|
+
base = join5(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE2);
|
|
30186
30510
|
}
|
|
30187
30511
|
return {
|
|
30188
30512
|
base,
|
|
30189
|
-
symbolsDir:
|
|
30190
|
-
footprintsDir:
|
|
30191
|
-
models3dDir:
|
|
30192
|
-
footprintDir:
|
|
30193
|
-
models3dFullDir:
|
|
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)
|
|
30194
30518
|
};
|
|
30195
30519
|
}
|
|
30196
30520
|
function getProjectLibraryPaths(projectPath) {
|
|
30197
|
-
const librariesDir =
|
|
30521
|
+
const librariesDir = join5(projectPath, "libraries");
|
|
30198
30522
|
return {
|
|
30199
30523
|
base: librariesDir,
|
|
30200
|
-
symbolsDir:
|
|
30201
|
-
footprintsDir:
|
|
30202
|
-
models3dDir:
|
|
30203
|
-
footprintDir:
|
|
30204
|
-
models3dFullDir:
|
|
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)
|
|
30205
30529
|
};
|
|
30206
30530
|
}
|
|
30207
|
-
function
|
|
30531
|
+
function isLcscId3(id) {
|
|
30208
30532
|
return /^C\d+$/.test(id);
|
|
30209
30533
|
}
|
|
30210
|
-
function
|
|
30211
|
-
const home =
|
|
30212
|
-
const plat =
|
|
30534
|
+
function getKicadConfigDir3(version2) {
|
|
30535
|
+
const home = homedir3();
|
|
30536
|
+
const plat = platform3();
|
|
30213
30537
|
if (plat === "darwin") {
|
|
30214
|
-
return
|
|
30538
|
+
return join5(home, "Library", "Preferences", "kicad", version2);
|
|
30215
30539
|
} else if (plat === "win32") {
|
|
30216
|
-
return
|
|
30540
|
+
return join5(process.env.APPDATA || join5(home, "AppData", "Roaming"), "kicad", version2);
|
|
30217
30541
|
} else {
|
|
30218
|
-
return
|
|
30542
|
+
return join5(home, ".config", "kicad", version2);
|
|
30219
30543
|
}
|
|
30220
30544
|
}
|
|
30221
30545
|
async function parseSymbolLibrary(filePath, libraryName, category, models3dDir) {
|
|
@@ -30241,8 +30565,8 @@ async function parseSymbolLibrary(filePath, libraryName, category, models3dDir)
|
|
|
30241
30565
|
const name = nameParts.length > 1 ? nameParts[1] : fullSymbolRef;
|
|
30242
30566
|
const fpMatch = symbolBlock.match(/\(property\s+"Footprint"\s+"([^"]+)"/);
|
|
30243
30567
|
const footprintRef = fpMatch ? fpMatch[1] : "";
|
|
30244
|
-
const model3dPath =
|
|
30245
|
-
const has3dModel =
|
|
30568
|
+
const model3dPath = join5(models3dDir, `${name}.step`);
|
|
30569
|
+
const has3dModel = existsSync4(model3dPath);
|
|
30246
30570
|
if (lcscId) {
|
|
30247
30571
|
components.push({
|
|
30248
30572
|
lcscId,
|
|
@@ -30258,6 +30582,42 @@ async function parseSymbolLibrary(filePath, libraryName, category, models3dDir)
|
|
|
30258
30582
|
} catch {}
|
|
30259
30583
|
return components;
|
|
30260
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
|
+
}
|
|
30261
30621
|
function adaptCommunityComponent(component) {
|
|
30262
30622
|
const symbolHead = component.symbol.head;
|
|
30263
30623
|
const cPara = symbolHead?.c_para || {};
|
|
@@ -30280,6 +30640,7 @@ function adaptCommunityComponent(component) {
|
|
|
30280
30640
|
polylines: component.symbol.polylines,
|
|
30281
30641
|
polygons: component.symbol.polygons,
|
|
30282
30642
|
paths: component.symbol.paths,
|
|
30643
|
+
texts: component.symbol.texts || [],
|
|
30283
30644
|
origin: component.symbol.origin
|
|
30284
30645
|
},
|
|
30285
30646
|
footprint: {
|
|
@@ -30303,14 +30664,11 @@ function adaptCommunityComponent(component) {
|
|
|
30303
30664
|
function createLibraryService() {
|
|
30304
30665
|
return {
|
|
30305
30666
|
async install(id, options = {}) {
|
|
30306
|
-
const isCommunityComponent = !
|
|
30307
|
-
|
|
30308
|
-
throw new Error("EasyEDA community components require projectPath for local storage");
|
|
30309
|
-
}
|
|
30310
|
-
const isGlobal = !isCommunityComponent && !options.projectPath;
|
|
30667
|
+
const isCommunityComponent = !isLcscId3(id);
|
|
30668
|
+
const isGlobal = !options.projectPath;
|
|
30311
30669
|
const paths = isGlobal ? getGlobalLibraryPaths() : getProjectLibraryPaths(options.projectPath);
|
|
30312
30670
|
let component = null;
|
|
30313
|
-
if (
|
|
30671
|
+
if (isLcscId3(id)) {
|
|
30314
30672
|
component = await easyedaClient.getComponentData(id);
|
|
30315
30673
|
} else {
|
|
30316
30674
|
const communityComponent = await easyedaCommunityClient.getComponent(id);
|
|
@@ -30350,13 +30708,27 @@ function createLibraryService() {
|
|
|
30350
30708
|
let category;
|
|
30351
30709
|
let modelPath;
|
|
30352
30710
|
if (isCommunityComponent) {
|
|
30353
|
-
const
|
|
30354
|
-
const
|
|
30355
|
-
|
|
30356
|
-
|
|
30711
|
+
const libName = isGlobal ? EASYEDA_LIBRARY_NAME2 : EASYEDA_LOCAL_LIBRARY_NAME;
|
|
30712
|
+
const symLibFile = isGlobal ? EASYEDA_SYMBOL_LIBRARY_NAME2 : EASYEDA_LOCAL_SYMBOL_LIBRARY_NAME;
|
|
30713
|
+
const fpLibDir = isGlobal ? EASYEDA_FOOTPRINT_LIBRARY_NAME2 : EASYEDA_LOCAL_FOOTPRINT_LIBRARY_NAME;
|
|
30714
|
+
const libDesc = isGlobal ? EASYEDA_LIBRARY_DESCRIPTION2 : EASYEDA_LOCAL_LIBRARY_DESCRIPTION;
|
|
30715
|
+
const models3dDirName = isGlobal ? "EasyEDA.3dshapes" : "EasyEDA-local.3dshapes";
|
|
30716
|
+
let symbolsDir;
|
|
30717
|
+
let easyedaModelsDir;
|
|
30718
|
+
if (isGlobal) {
|
|
30719
|
+
symbolsDir = paths.symbolsDir;
|
|
30720
|
+
footprintDir = join5(paths.footprintsDir, fpLibDir);
|
|
30721
|
+
easyedaModelsDir = join5(paths.models3dDir, models3dDirName);
|
|
30722
|
+
} else {
|
|
30723
|
+
const librariesDir = join5(options.projectPath, "libraries");
|
|
30724
|
+
symbolsDir = join5(librariesDir, "symbols");
|
|
30725
|
+
footprintDir = join5(librariesDir, "footprints", fpLibDir);
|
|
30726
|
+
easyedaModelsDir = join5(librariesDir, "3dmodels", models3dDirName);
|
|
30727
|
+
}
|
|
30728
|
+
models3dDir = easyedaModelsDir;
|
|
30357
30729
|
await ensureDir(symbolsDir);
|
|
30358
30730
|
await ensureDir(footprintDir);
|
|
30359
|
-
symbolFile =
|
|
30731
|
+
symbolFile = join5(symbolsDir, symLibFile);
|
|
30360
30732
|
symbolName = component.info.name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30361
30733
|
const include3d = options.include3d ?? true;
|
|
30362
30734
|
let modelRelativePath;
|
|
@@ -30365,40 +30737,49 @@ function createLibraryService() {
|
|
|
30365
30737
|
const model = await easyedaCommunityClient.get3DModel(component.model3d.uuid, "step");
|
|
30366
30738
|
if (model) {
|
|
30367
30739
|
const modelFilename = `${symbolName}.step`;
|
|
30368
|
-
modelPath =
|
|
30740
|
+
modelPath = join5(models3dDir, modelFilename);
|
|
30369
30741
|
await writeBinary(modelPath, model);
|
|
30370
|
-
|
|
30742
|
+
if (isGlobal) {
|
|
30743
|
+
modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${models3dDirName}/${modelFilename}`;
|
|
30744
|
+
} else {
|
|
30745
|
+
modelRelativePath = `\${KIPRJMOD}/libraries/3dmodels/${models3dDirName}/${modelFilename}`;
|
|
30746
|
+
}
|
|
30371
30747
|
}
|
|
30372
30748
|
}
|
|
30373
30749
|
const footprint = footprintConverter.convert(component, {
|
|
30374
|
-
libraryName:
|
|
30750
|
+
libraryName: libName,
|
|
30375
30751
|
include3DModel: !!modelRelativePath,
|
|
30376
30752
|
modelPath: modelRelativePath
|
|
30377
30753
|
});
|
|
30378
|
-
footprintPath =
|
|
30379
|
-
footprintRef = `${
|
|
30754
|
+
footprintPath = join5(footprintDir, `${symbolName}.kicad_mod`);
|
|
30755
|
+
footprintRef = `${libName}:${symbolName}`;
|
|
30380
30756
|
await writeText(footprintPath, footprint);
|
|
30381
30757
|
component.info.package = footprintRef;
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30758
|
+
if (isGlobal) {
|
|
30759
|
+
const { ensureGlobalEasyEDALibrary: ensureGlobalEasyEDALibrary2 } = await Promise.resolve().then(() => (init_global_lib_table(), exports_global_lib_table));
|
|
30760
|
+
await ensureGlobalEasyEDALibrary2();
|
|
30761
|
+
} else {
|
|
30762
|
+
await ensureSymLibTable(options.projectPath, symbolFile, libName, libDesc);
|
|
30763
|
+
await ensureFpLibTable(options.projectPath, footprintDir, libName, libDesc);
|
|
30764
|
+
}
|
|
30765
|
+
symbolRef = `${libName}:${symbolName}`;
|
|
30385
30766
|
} else {
|
|
30386
30767
|
category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
|
|
30387
30768
|
const symbolLibraryFilename = getLibraryFilename(category);
|
|
30388
|
-
symbolFile =
|
|
30769
|
+
symbolFile = join5(paths.symbolsDir, symbolLibraryFilename);
|
|
30389
30770
|
footprintDir = paths.footprintDir;
|
|
30390
30771
|
models3dDir = paths.models3dFullDir;
|
|
30391
30772
|
await ensureDir(paths.symbolsDir);
|
|
30392
30773
|
await ensureDir(paths.footprintDir);
|
|
30393
30774
|
symbolName = symbolConverter.getSymbolName(component);
|
|
30394
|
-
const include3d = options.include3d ??
|
|
30775
|
+
const include3d = options.include3d ?? true;
|
|
30395
30776
|
let modelRelativePath;
|
|
30396
30777
|
if (include3d && component.model3d) {
|
|
30397
30778
|
await ensureDir(models3dDir);
|
|
30398
30779
|
const model = await easyedaClient.get3DModel(component.model3d.uuid, "step");
|
|
30399
30780
|
if (model) {
|
|
30400
30781
|
const modelFilename = `${symbolName}.step`;
|
|
30401
|
-
modelPath =
|
|
30782
|
+
modelPath = join5(models3dDir, modelFilename);
|
|
30402
30783
|
await writeBinary(modelPath, model);
|
|
30403
30784
|
modelRelativePath = `\${KICAD9_3RD_PARTY}/${LIBRARY_NAMESPACE2}/3dmodels/${MODELS_3D_NAME}/${modelFilename}`;
|
|
30404
30785
|
}
|
|
@@ -30410,7 +30791,7 @@ function createLibraryService() {
|
|
|
30410
30791
|
if (footprintResult.type === "reference") {
|
|
30411
30792
|
footprintRef = footprintResult.reference;
|
|
30412
30793
|
} else {
|
|
30413
|
-
footprintPath =
|
|
30794
|
+
footprintPath = join5(footprintDir, `${footprintResult.name}.kicad_mod`);
|
|
30414
30795
|
footprintRef = getFootprintReference(footprintResult.name);
|
|
30415
30796
|
await writeText(footprintPath, footprintResult.content);
|
|
30416
30797
|
}
|
|
@@ -30423,12 +30804,12 @@ function createLibraryService() {
|
|
|
30423
30804
|
}
|
|
30424
30805
|
let symbolContent;
|
|
30425
30806
|
let symbolAction;
|
|
30426
|
-
if (
|
|
30807
|
+
if (existsSync4(symbolFile)) {
|
|
30427
30808
|
const existingContent = await readFile4(symbolFile, "utf-8");
|
|
30428
30809
|
if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
|
|
30429
30810
|
if (options.force) {
|
|
30430
30811
|
symbolContent = symbolConverter.replaceInLibrary(existingContent, component, {
|
|
30431
|
-
libraryName: isCommunityComponent ?
|
|
30812
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30432
30813
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30433
30814
|
});
|
|
30434
30815
|
symbolAction = "replaced";
|
|
@@ -30438,14 +30819,14 @@ function createLibraryService() {
|
|
|
30438
30819
|
}
|
|
30439
30820
|
} else {
|
|
30440
30821
|
symbolContent = symbolConverter.appendToLibrary(existingContent, component, {
|
|
30441
|
-
libraryName: isCommunityComponent ?
|
|
30822
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30442
30823
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30443
30824
|
});
|
|
30444
30825
|
symbolAction = "appended";
|
|
30445
30826
|
}
|
|
30446
30827
|
} else {
|
|
30447
30828
|
symbolContent = symbolConverter.convert(component, {
|
|
30448
|
-
libraryName: isCommunityComponent ?
|
|
30829
|
+
libraryName: isCommunityComponent ? EASYEDA_LIBRARY_NAME2 : undefined,
|
|
30449
30830
|
symbolName: isCommunityComponent ? symbolName : undefined
|
|
30450
30831
|
});
|
|
30451
30832
|
symbolAction = "created";
|
|
@@ -30515,9 +30896,9 @@ function createLibraryService() {
|
|
|
30515
30896
|
if (options.category && options.category !== category)
|
|
30516
30897
|
continue;
|
|
30517
30898
|
const libraryFilename = getLibraryFilename(category);
|
|
30518
|
-
const libraryPath =
|
|
30899
|
+
const libraryPath = join5(paths.symbolsDir, libraryFilename);
|
|
30519
30900
|
const libraryName = `JLC-MCP-${category}`;
|
|
30520
|
-
if (
|
|
30901
|
+
if (existsSync4(libraryPath)) {
|
|
30521
30902
|
const components = await parseSymbolLibrary(libraryPath, libraryName, category, paths.models3dFullDir);
|
|
30522
30903
|
allComponents.push(...components);
|
|
30523
30904
|
}
|
|
@@ -30532,13 +30913,13 @@ function createLibraryService() {
|
|
|
30532
30913
|
await ensureGlobalLibraryTables2();
|
|
30533
30914
|
},
|
|
30534
30915
|
async getStatus() {
|
|
30535
|
-
const version2 =
|
|
30916
|
+
const version2 = detectKicadVersion();
|
|
30536
30917
|
const paths = getGlobalLibraryPaths();
|
|
30537
|
-
const configDir =
|
|
30538
|
-
const symLibTablePath =
|
|
30539
|
-
const fpLibTablePath =
|
|
30918
|
+
const configDir = getKicadConfigDir3(version2);
|
|
30919
|
+
const symLibTablePath = join5(configDir, "sym-lib-table");
|
|
30920
|
+
const fpLibTablePath = join5(configDir, "fp-lib-table");
|
|
30540
30921
|
let installed = false;
|
|
30541
|
-
if (
|
|
30922
|
+
if (existsSync4(paths.symbolsDir)) {
|
|
30542
30923
|
try {
|
|
30543
30924
|
const files = await readdir2(paths.symbolsDir);
|
|
30544
30925
|
installed = files.some((f) => f.endsWith(".kicad_sym"));
|
|
@@ -30547,7 +30928,7 @@ function createLibraryService() {
|
|
|
30547
30928
|
}
|
|
30548
30929
|
}
|
|
30549
30930
|
let linked = false;
|
|
30550
|
-
if (
|
|
30931
|
+
if (existsSync4(symLibTablePath)) {
|
|
30551
30932
|
try {
|
|
30552
30933
|
const content = await readFile4(symLibTablePath, "utf-8");
|
|
30553
30934
|
linked = content.includes("JLC-MCP");
|
|
@@ -30570,148 +30951,437 @@ function createLibraryService() {
|
|
|
30570
30951
|
fpLibTable: fpLibTablePath
|
|
30571
30952
|
}
|
|
30572
30953
|
};
|
|
30954
|
+
},
|
|
30955
|
+
async isEasyEDAInstalled(componentName) {
|
|
30956
|
+
const paths = getGlobalLibraryPaths();
|
|
30957
|
+
const easyedaLibPath = join5(paths.symbolsDir, EASYEDA_SYMBOL_LIBRARY_NAME2);
|
|
30958
|
+
if (!existsSync4(easyedaLibPath)) {
|
|
30959
|
+
return false;
|
|
30960
|
+
}
|
|
30961
|
+
try {
|
|
30962
|
+
const content = await readFile4(easyedaLibPath, "utf-8");
|
|
30963
|
+
const sanitizedName = componentName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30964
|
+
const pattern = new RegExp(`\\(symbol\\s+"${sanitizedName}"`, "m");
|
|
30965
|
+
return pattern.test(content);
|
|
30966
|
+
} catch {
|
|
30967
|
+
return false;
|
|
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;
|
|
30573
31048
|
}
|
|
30574
31049
|
};
|
|
30575
31050
|
}
|
|
30576
31051
|
init_lib_table();
|
|
30577
31052
|
init_category_router();
|
|
30578
31053
|
init_global_lib_table();
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
var
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
|
|
30585
|
-
|
|
30586
|
-
|
|
30587
|
-
|
|
30588
|
-
|
|
30589
|
-
|
|
30590
|
-
|
|
30591
|
-
|
|
30592
|
-
|
|
30593
|
-
|
|
30594
|
-
|
|
30595
|
-
|
|
30596
|
-
|
|
30597
|
-
|
|
30598
|
-
|
|
30599
|
-
|
|
30600
|
-
|
|
30601
|
-
|
|
30602
|
-
|
|
30603
|
-
|
|
30604
|
-
|
|
31054
|
+
var logger7 = createLogger("http-routes");
|
|
31055
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
31056
|
+
var __dirname2 = dirname3(__filename2);
|
|
31057
|
+
var htmlCache = null;
|
|
31058
|
+
function getHtmlPage() {
|
|
31059
|
+
if (htmlCache)
|
|
31060
|
+
return htmlCache;
|
|
31061
|
+
try {
|
|
31062
|
+
const possiblePaths = [
|
|
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")
|
|
31073
|
+
];
|
|
31074
|
+
for (const path of possiblePaths) {
|
|
31075
|
+
try {
|
|
31076
|
+
htmlCache = readFileSync(path, "utf-8");
|
|
31077
|
+
logger7.debug(`Loaded HTML from: ${path}`);
|
|
31078
|
+
return htmlCache;
|
|
31079
|
+
} catch {}
|
|
31080
|
+
}
|
|
31081
|
+
throw new Error("HTML file not found");
|
|
31082
|
+
} catch (error2) {
|
|
31083
|
+
logger7.error("Failed to load HTML page:", error2);
|
|
31084
|
+
return `<!DOCTYPE html>
|
|
31085
|
+
<html>
|
|
31086
|
+
<head><title>Error</title></head>
|
|
31087
|
+
<body>
|
|
31088
|
+
<h1>Error: Search page not found</h1>
|
|
31089
|
+
<p>The search page has not been built. Run: bun run build</p>
|
|
31090
|
+
</body>
|
|
31091
|
+
</html>`;
|
|
30605
31092
|
}
|
|
30606
|
-
};
|
|
30607
|
-
var SearchParamsSchema = exports_external.object({
|
|
30608
|
-
query: exports_external.string().min(1),
|
|
30609
|
-
limit: exports_external.number().min(1).max(50).default(10),
|
|
30610
|
-
in_stock: exports_external.boolean().optional(),
|
|
30611
|
-
basic_only: exports_external.boolean().optional()
|
|
30612
|
-
});
|
|
30613
|
-
async function handleSearchComponents(args) {
|
|
30614
|
-
const params = SearchParamsSchema.parse(args);
|
|
30615
|
-
const results = await jlcClient.search(params.query, {
|
|
30616
|
-
limit: params.limit,
|
|
30617
|
-
inStock: params.in_stock,
|
|
30618
|
-
basicOnly: params.basic_only
|
|
30619
|
-
});
|
|
30620
|
-
return {
|
|
30621
|
-
content: [{
|
|
30622
|
-
type: "text",
|
|
30623
|
-
text: JSON.stringify(results, null, 2)
|
|
30624
|
-
}]
|
|
30625
|
-
};
|
|
30626
31093
|
}
|
|
30627
|
-
|
|
30628
|
-
|
|
30629
|
-
|
|
30630
|
-
|
|
30631
|
-
|
|
30632
|
-
|
|
30633
|
-
|
|
30634
|
-
|
|
30635
|
-
|
|
30636
|
-
|
|
30637
|
-
|
|
30638
|
-
|
|
30639
|
-
|
|
30640
|
-
|
|
31094
|
+
async function handleRequest(req, res) {
|
|
31095
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
31096
|
+
const pathname = url.pathname;
|
|
31097
|
+
logger7.debug(`${req.method} ${pathname}`);
|
|
31098
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
31099
|
+
const query = url.searchParams.get("q") || undefined;
|
|
31100
|
+
serveHtml(res, query);
|
|
31101
|
+
} else if (pathname === "/api/search") {
|
|
31102
|
+
await handleSearch(url, res);
|
|
31103
|
+
} else if (pathname.startsWith("/api/component/")) {
|
|
31104
|
+
const uuid2 = pathname.replace("/api/component/", "");
|
|
31105
|
+
await handleComponent(uuid2, res);
|
|
31106
|
+
} else if (pathname === "/health") {
|
|
31107
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31108
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
31109
|
+
} else {
|
|
31110
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
31111
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
30641
31112
|
}
|
|
30642
|
-
}
|
|
30643
|
-
|
|
30644
|
-
|
|
30645
|
-
|
|
30646
|
-
|
|
30647
|
-
|
|
30648
|
-
const component = await easyedaClient.getComponentData(params.lcsc_id);
|
|
30649
|
-
if (!component) {
|
|
30650
|
-
return {
|
|
30651
|
-
content: [{
|
|
30652
|
-
type: "text",
|
|
30653
|
-
text: `Component ${params.lcsc_id} not found`
|
|
30654
|
-
}],
|
|
30655
|
-
isError: true
|
|
30656
|
-
};
|
|
31113
|
+
}
|
|
31114
|
+
function serveHtml(res, initialQuery) {
|
|
31115
|
+
let html = getHtmlPage();
|
|
31116
|
+
if (initialQuery) {
|
|
31117
|
+
const script = `<script>window.__INITIAL_QUERY__ = ${JSON.stringify(initialQuery)};</script>`;
|
|
31118
|
+
html = html.replace("</head>", `${script}</head>`);
|
|
30657
31119
|
}
|
|
30658
|
-
|
|
30659
|
-
|
|
30660
|
-
symbol: {
|
|
30661
|
-
pin_count: component.symbol.pins.length,
|
|
30662
|
-
pins: component.symbol.pins
|
|
30663
|
-
},
|
|
30664
|
-
footprint: {
|
|
30665
|
-
name: component.footprint.name,
|
|
30666
|
-
type: component.footprint.type,
|
|
30667
|
-
pad_count: component.footprint.pads.length,
|
|
30668
|
-
pads: component.footprint.pads
|
|
30669
|
-
},
|
|
30670
|
-
has_3d_model: !!component.model3d,
|
|
30671
|
-
model_3d_uuid: component.model3d?.uuid
|
|
30672
|
-
};
|
|
30673
|
-
return {
|
|
30674
|
-
content: [{
|
|
30675
|
-
type: "text",
|
|
30676
|
-
text: JSON.stringify(summary, null, 2)
|
|
30677
|
-
}]
|
|
30678
|
-
};
|
|
31120
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
31121
|
+
res.end(html);
|
|
30679
31122
|
}
|
|
30680
|
-
|
|
30681
|
-
|
|
30682
|
-
|
|
30683
|
-
|
|
30684
|
-
|
|
30685
|
-
|
|
30686
|
-
|
|
30687
|
-
|
|
30688
|
-
|
|
30689
|
-
|
|
30690
|
-
|
|
30691
|
-
|
|
30692
|
-
|
|
31123
|
+
async function handleSearch(url, res) {
|
|
31124
|
+
const query = url.searchParams.get("q") || "";
|
|
31125
|
+
const source = url.searchParams.get("source") || "user";
|
|
31126
|
+
const page = parseInt(url.searchParams.get("page") || "1", 10);
|
|
31127
|
+
const limit = parseInt(url.searchParams.get("limit") || "20", 10);
|
|
31128
|
+
if (!query) {
|
|
31129
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
31130
|
+
res.end(JSON.stringify({ error: "Missing query parameter" }));
|
|
31131
|
+
return;
|
|
31132
|
+
}
|
|
31133
|
+
try {
|
|
31134
|
+
const allResults = await easyedaCommunityClient.search({
|
|
31135
|
+
query,
|
|
31136
|
+
source,
|
|
31137
|
+
limit: Math.min(limit * page + limit, 100)
|
|
31138
|
+
});
|
|
31139
|
+
const startIndex = (page - 1) * limit;
|
|
31140
|
+
const endIndex = startIndex + limit;
|
|
31141
|
+
const results = allResults.slice(startIndex, endIndex);
|
|
31142
|
+
const totalPages = Math.ceil(allResults.length / limit);
|
|
31143
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31144
|
+
res.end(JSON.stringify({
|
|
31145
|
+
results,
|
|
31146
|
+
pagination: {
|
|
31147
|
+
page,
|
|
31148
|
+
limit,
|
|
31149
|
+
total: allResults.length,
|
|
31150
|
+
totalPages,
|
|
31151
|
+
hasNext: page < totalPages,
|
|
31152
|
+
hasPrev: page > 1
|
|
30693
31153
|
}
|
|
31154
|
+
}));
|
|
31155
|
+
} catch (error2) {
|
|
31156
|
+
logger7.error("Search error:", error2);
|
|
31157
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
31158
|
+
res.end(JSON.stringify({ error: "Search failed" }));
|
|
31159
|
+
}
|
|
31160
|
+
}
|
|
31161
|
+
function communityToComponentData(community) {
|
|
31162
|
+
const cPara = community.symbol.head?.c_para ?? {};
|
|
31163
|
+
const fpCPara = community.footprint.head?.c_para ?? {};
|
|
31164
|
+
return {
|
|
31165
|
+
info: {
|
|
31166
|
+
name: community.title || cPara.name || "Unknown",
|
|
31167
|
+
prefix: cPara.pre || cPara.Prefix || "U",
|
|
31168
|
+
package: fpCPara.package || community.footprint.name,
|
|
31169
|
+
manufacturer: cPara.Manufacturer || cPara.BOM_Manufacturer,
|
|
31170
|
+
description: community.description || cPara.BOM_Manufacturer_Part,
|
|
31171
|
+
category: cPara.package
|
|
30694
31172
|
},
|
|
30695
|
-
|
|
31173
|
+
symbol: community.symbol,
|
|
31174
|
+
footprint: community.footprint,
|
|
31175
|
+
model3d: community.model3d,
|
|
31176
|
+
rawData: community.rawData
|
|
31177
|
+
};
|
|
31178
|
+
}
|
|
31179
|
+
async function handleComponent(uuid2, res) {
|
|
31180
|
+
if (!uuid2) {
|
|
31181
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
31182
|
+
res.end(JSON.stringify({ error: "Missing UUID" }));
|
|
31183
|
+
return;
|
|
30696
31184
|
}
|
|
30697
|
-
|
|
30698
|
-
|
|
30699
|
-
|
|
30700
|
-
|
|
31185
|
+
try {
|
|
31186
|
+
const component2 = await easyedaCommunityClient.getComponent(uuid2);
|
|
31187
|
+
if (!component2) {
|
|
31188
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
31189
|
+
res.end(JSON.stringify({ error: "Component not found" }));
|
|
31190
|
+
return;
|
|
31191
|
+
}
|
|
31192
|
+
const componentData = communityToComponentData(component2);
|
|
31193
|
+
let symbolSexpr = "";
|
|
31194
|
+
let footprintSexpr = "";
|
|
31195
|
+
try {
|
|
31196
|
+
symbolSexpr = symbolConverter.convertToSymbolEntry(componentData);
|
|
31197
|
+
} catch (e) {
|
|
31198
|
+
logger7.warn("Symbol conversion failed:", e);
|
|
31199
|
+
}
|
|
31200
|
+
try {
|
|
31201
|
+
footprintSexpr = footprintConverter.convert(componentData);
|
|
31202
|
+
} catch (e) {
|
|
31203
|
+
logger7.warn("Footprint conversion failed:", e);
|
|
31204
|
+
}
|
|
31205
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31206
|
+
res.end(JSON.stringify({
|
|
31207
|
+
uuid: component2.uuid,
|
|
31208
|
+
title: component2.title,
|
|
31209
|
+
description: component2.description,
|
|
31210
|
+
owner: component2.owner,
|
|
31211
|
+
symbolSexpr,
|
|
31212
|
+
footprintSexpr,
|
|
31213
|
+
model3d: component2.model3d
|
|
31214
|
+
}));
|
|
31215
|
+
} catch (error2) {
|
|
31216
|
+
logger7.error("Component fetch error:", error2);
|
|
31217
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
31218
|
+
res.end(JSON.stringify({ error: "Failed to fetch component" }));
|
|
31219
|
+
}
|
|
31220
|
+
}
|
|
31221
|
+
var logger8 = createLogger("http-server");
|
|
31222
|
+
var DEFAULT_PORT = 3847;
|
|
31223
|
+
var serverInstance = null;
|
|
31224
|
+
function startHttpServer(options = {}) {
|
|
31225
|
+
if (serverInstance) {
|
|
31226
|
+
logger8.debug("HTTP server already running");
|
|
31227
|
+
const port2 = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
|
|
31228
|
+
options.onReady?.(`http://localhost:${port2}`);
|
|
31229
|
+
return port2;
|
|
31230
|
+
}
|
|
31231
|
+
const port = options.port ?? parseInt(process.env.JLC_MCP_HTTP_PORT || String(DEFAULT_PORT), 10);
|
|
31232
|
+
serverInstance = createServer(async (req, res) => {
|
|
31233
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
31234
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
31235
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
31236
|
+
if (req.method === "OPTIONS") {
|
|
31237
|
+
res.writeHead(204);
|
|
31238
|
+
res.end();
|
|
31239
|
+
return;
|
|
31240
|
+
}
|
|
31241
|
+
try {
|
|
31242
|
+
await handleRequest(req, res);
|
|
31243
|
+
} catch (error2) {
|
|
31244
|
+
logger8.error("Request error:", error2);
|
|
31245
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
31246
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
31247
|
+
}
|
|
31248
|
+
});
|
|
31249
|
+
serverInstance.listen(port, () => {
|
|
31250
|
+
const url = `http://localhost:${port}`;
|
|
31251
|
+
logger8.info(`HTTP server listening on ${url}`);
|
|
31252
|
+
options.onReady?.(url);
|
|
31253
|
+
});
|
|
31254
|
+
serverInstance.on("error", (error2) => {
|
|
31255
|
+
if (error2.code === "EADDRINUSE") {
|
|
31256
|
+
logger8.warn(`Port ${port} already in use, HTTP server not started`);
|
|
31257
|
+
} else {
|
|
31258
|
+
logger8.error("HTTP server error:", error2);
|
|
31259
|
+
}
|
|
31260
|
+
});
|
|
31261
|
+
return port;
|
|
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
|
+
}
|
|
31282
|
+
|
|
31283
|
+
// src/tools/search.ts
|
|
31284
|
+
var componentSearchTool = {
|
|
31285
|
+
name: "component_search",
|
|
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.)`,
|
|
30701
31297
|
inputSchema: {
|
|
30702
31298
|
type: "object",
|
|
30703
31299
|
properties: {
|
|
30704
|
-
|
|
31300
|
+
query: {
|
|
30705
31301
|
type: "string",
|
|
30706
|
-
description:
|
|
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'
|
|
31308
|
+
},
|
|
31309
|
+
limit: {
|
|
31310
|
+
type: "number",
|
|
31311
|
+
description: "Maximum number of results (default: 10, max: 50)"
|
|
31312
|
+
},
|
|
31313
|
+
in_stock: {
|
|
31314
|
+
type: "boolean",
|
|
31315
|
+
description: "Only show in-stock items (default: false, LCSC only)"
|
|
31316
|
+
},
|
|
31317
|
+
basic_only: {
|
|
31318
|
+
type: "boolean",
|
|
31319
|
+
description: "Only show JLCPCB Basic Parts Library components (lower assembly cost, LCSC only)"
|
|
30707
31320
|
}
|
|
30708
31321
|
},
|
|
30709
|
-
required: ["
|
|
31322
|
+
required: ["query"]
|
|
30710
31323
|
}
|
|
30711
31324
|
};
|
|
30712
|
-
var
|
|
30713
|
-
|
|
30714
|
-
|
|
31325
|
+
var SearchParamsSchema = exports_external.object({
|
|
31326
|
+
query: exports_external.string().min(1),
|
|
31327
|
+
source: exports_external.enum(["lcsc", "community"]).default("lcsc"),
|
|
31328
|
+
limit: exports_external.number().min(1).max(50).default(10),
|
|
31329
|
+
in_stock: exports_external.boolean().optional(),
|
|
31330
|
+
basic_only: exports_external.boolean().optional()
|
|
31331
|
+
});
|
|
31332
|
+
async function handleComponentSearch(args) {
|
|
31333
|
+
const params = SearchParamsSchema.parse(args);
|
|
31334
|
+
const results = await getComponentService().search(params.query, {
|
|
31335
|
+
source: params.source,
|
|
31336
|
+
limit: params.limit,
|
|
31337
|
+
inStock: params.in_stock,
|
|
31338
|
+
basicOnly: params.basic_only
|
|
31339
|
+
});
|
|
31340
|
+
if (results.length === 0) {
|
|
31341
|
+
return {
|
|
31342
|
+
content: [{
|
|
31343
|
+
type: "text",
|
|
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
|
+
}]
|
|
31353
|
+
};
|
|
31354
|
+
}
|
|
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
|
+
}));
|
|
31368
|
+
return {
|
|
31369
|
+
content: [{
|
|
31370
|
+
type: "text",
|
|
31371
|
+
text: JSON.stringify({
|
|
31372
|
+
success: true,
|
|
31373
|
+
query: params.query,
|
|
31374
|
+
source: params.source,
|
|
31375
|
+
count: results.length,
|
|
31376
|
+
results: compactResults
|
|
31377
|
+
})
|
|
31378
|
+
}]
|
|
31379
|
+
};
|
|
31380
|
+
}
|
|
31381
|
+
// src/tools/library.ts
|
|
31382
|
+
var libraryInstallTool = {
|
|
31383
|
+
name: "library_install",
|
|
31384
|
+
description: `Install a component to KiCad libraries.
|
|
30715
31385
|
|
|
30716
31386
|
Accepts:
|
|
30717
31387
|
- LCSC part numbers (e.g., C2040) → global JLC-MCP libraries
|
|
@@ -30721,11 +31391,7 @@ LCSC components are routed to category-based global libraries:
|
|
|
30721
31391
|
- JLC-MCP-Resistors.kicad_sym, JLC-MCP-Capacitors.kicad_sym, JLC-MCP-ICs.kicad_sym, etc.
|
|
30722
31392
|
- Stored at ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
|
|
30723
31393
|
|
|
30724
|
-
|
|
30725
|
-
- <project>/libraries/symbols/EasyEDA.kicad_sym
|
|
30726
|
-
- <project>/libraries/footprints/EasyEDA.pretty/
|
|
30727
|
-
|
|
30728
|
-
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.`,
|
|
30729
31395
|
inputSchema: {
|
|
30730
31396
|
type: "object",
|
|
30731
31397
|
properties: {
|
|
@@ -30739,89 +31405,47 @@ Returns symbol_ref and footprint_ref for immediate use with add_schematic_compon
|
|
|
30739
31405
|
},
|
|
30740
31406
|
include_3d: {
|
|
30741
31407
|
type: "boolean",
|
|
30742
|
-
description: "Include 3D model if available (default: false
|
|
31408
|
+
description: "Include 3D model if available (default: false)"
|
|
31409
|
+
},
|
|
31410
|
+
force: {
|
|
31411
|
+
type: "boolean",
|
|
31412
|
+
description: "Reinstall even if already exists (default: false)"
|
|
30743
31413
|
}
|
|
30744
31414
|
},
|
|
30745
31415
|
required: ["id"]
|
|
30746
31416
|
}
|
|
30747
31417
|
};
|
|
30748
|
-
var
|
|
30749
|
-
name: "
|
|
30750
|
-
description:
|
|
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.`,
|
|
30751
31426
|
inputSchema: {
|
|
30752
31427
|
type: "object",
|
|
30753
31428
|
properties: {
|
|
30754
|
-
|
|
30755
|
-
type: "string",
|
|
30756
|
-
description: "3D model UUID from component_get result"
|
|
30757
|
-
},
|
|
30758
|
-
format: {
|
|
31429
|
+
id: {
|
|
30759
31430
|
type: "string",
|
|
30760
|
-
|
|
30761
|
-
description: 'Model format: "step" or "obj" (default: step)'
|
|
31431
|
+
description: "LCSC part number (e.g., C2040)"
|
|
30762
31432
|
}
|
|
30763
31433
|
},
|
|
30764
|
-
required: ["
|
|
31434
|
+
required: ["id"]
|
|
30765
31435
|
}
|
|
30766
31436
|
};
|
|
30767
|
-
var
|
|
30768
|
-
lcsc_id: exports_external.string().regex(/^C\d+$/, "Invalid LCSC part number")
|
|
30769
|
-
});
|
|
30770
|
-
var FetchLibraryParamsSchema = exports_external.object({
|
|
31437
|
+
var LibraryInstallParamsSchema = exports_external.object({
|
|
30771
31438
|
id: exports_external.string().min(1),
|
|
30772
|
-
project_path:
|
|
30773
|
-
include_3d: exports_external.boolean().optional()
|
|
31439
|
+
project_path: SafePathSchema.optional(),
|
|
31440
|
+
include_3d: exports_external.boolean().optional(),
|
|
31441
|
+
force: exports_external.boolean().optional()
|
|
30774
31442
|
});
|
|
30775
|
-
var
|
|
30776
|
-
|
|
30777
|
-
format: exports_external.enum(["step", "obj"]).default("step")
|
|
31443
|
+
var LibraryGetComponentParamsSchema = exports_external.object({
|
|
31444
|
+
id: LCSCPartNumberSchema
|
|
30778
31445
|
});
|
|
30779
|
-
async function
|
|
30780
|
-
const params =
|
|
30781
|
-
const
|
|
30782
|
-
if (!component) {
|
|
30783
|
-
return {
|
|
30784
|
-
content: [{
|
|
30785
|
-
type: "text",
|
|
30786
|
-
text: `Component ${params.lcsc_id} not found`
|
|
30787
|
-
}],
|
|
30788
|
-
isError: true
|
|
30789
|
-
};
|
|
30790
|
-
}
|
|
30791
|
-
const symbol = symbolConverter.convert(component);
|
|
30792
|
-
return {
|
|
30793
|
-
content: [{
|
|
30794
|
-
type: "text",
|
|
30795
|
-
text: symbol
|
|
30796
|
-
}]
|
|
30797
|
-
};
|
|
30798
|
-
}
|
|
30799
|
-
async function handleGetFootprintKicad(args) {
|
|
30800
|
-
const params = LibraryParamsSchema.parse(args);
|
|
30801
|
-
const component = await easyedaClient.getComponentData(params.lcsc_id);
|
|
30802
|
-
if (!component) {
|
|
30803
|
-
return {
|
|
30804
|
-
content: [{
|
|
30805
|
-
type: "text",
|
|
30806
|
-
text: `Component ${params.lcsc_id} not found`
|
|
30807
|
-
}],
|
|
30808
|
-
isError: true
|
|
30809
|
-
};
|
|
30810
|
-
}
|
|
30811
|
-
const footprint = footprintConverter.convert(component);
|
|
30812
|
-
return {
|
|
30813
|
-
content: [{
|
|
30814
|
-
type: "text",
|
|
30815
|
-
text: footprint
|
|
30816
|
-
}]
|
|
30817
|
-
};
|
|
30818
|
-
}
|
|
30819
|
-
function isLcscId3(id) {
|
|
30820
|
-
return /^C\d+$/.test(id);
|
|
30821
|
-
}
|
|
30822
|
-
async function handleFetchLibrary(args) {
|
|
30823
|
-
const params = FetchLibraryParamsSchema.parse(args);
|
|
30824
|
-
const isCommunityComponent = !isLcscId3(params.id);
|
|
31446
|
+
async function handleLibraryInstall(args) {
|
|
31447
|
+
const params = LibraryInstallParamsSchema.parse(args);
|
|
31448
|
+
const isCommunityComponent = !isLcscId(params.id);
|
|
30825
31449
|
if (isCommunityComponent && !params.project_path) {
|
|
30826
31450
|
return {
|
|
30827
31451
|
content: [{
|
|
@@ -30837,9 +31461,10 @@ async function handleFetchLibrary(args) {
|
|
|
30837
31461
|
};
|
|
30838
31462
|
}
|
|
30839
31463
|
try {
|
|
30840
|
-
const result = await
|
|
31464
|
+
const result = await getLibraryService().install(params.id, {
|
|
30841
31465
|
projectPath: params.project_path,
|
|
30842
|
-
include3d: params.include_3d
|
|
31466
|
+
include3d: params.include_3d,
|
|
31467
|
+
force: params.force
|
|
30843
31468
|
});
|
|
30844
31469
|
return {
|
|
30845
31470
|
content: [{
|
|
@@ -30847,22 +31472,23 @@ async function handleFetchLibrary(args) {
|
|
|
30847
31472
|
text: JSON.stringify({
|
|
30848
31473
|
success: true,
|
|
30849
31474
|
id: params.id,
|
|
30850
|
-
|
|
30851
|
-
storage_mode: result.storageMode,
|
|
30852
|
-
category: result.category,
|
|
30853
|
-
symbol_name: result.symbolName,
|
|
31475
|
+
installed: true,
|
|
30854
31476
|
symbol_ref: result.symbolRef,
|
|
30855
31477
|
footprint_ref: result.footprintRef,
|
|
30856
|
-
|
|
30857
|
-
datasheet: result.datasheet,
|
|
31478
|
+
category: result.category,
|
|
30858
31479
|
files: {
|
|
30859
31480
|
symbol_library: result.files.symbolLibrary,
|
|
30860
31481
|
footprint: result.files.footprint,
|
|
30861
31482
|
model_3d: result.files.model3d
|
|
30862
31483
|
},
|
|
30863
|
-
|
|
30864
|
-
|
|
30865
|
-
|
|
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
|
+
})
|
|
30866
31492
|
}]
|
|
30867
31493
|
};
|
|
30868
31494
|
} catch (error2) {
|
|
@@ -30872,76 +31498,202 @@ async function handleFetchLibrary(args) {
|
|
|
30872
31498
|
text: JSON.stringify({
|
|
30873
31499
|
success: false,
|
|
30874
31500
|
error: error2 instanceof Error ? error2.message : "Unknown error",
|
|
30875
|
-
id: params.id
|
|
30876
|
-
source: isCommunityComponent ? "easyeda_community" : "lcsc"
|
|
31501
|
+
id: params.id
|
|
30877
31502
|
})
|
|
30878
31503
|
}],
|
|
30879
31504
|
isError: true
|
|
30880
31505
|
};
|
|
30881
31506
|
}
|
|
30882
31507
|
}
|
|
30883
|
-
async function
|
|
30884
|
-
const params =
|
|
30885
|
-
|
|
30886
|
-
|
|
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
|
+
}
|
|
30887
31541
|
return {
|
|
30888
31542
|
content: [{
|
|
30889
31543
|
type: "text",
|
|
30890
|
-
text:
|
|
30891
|
-
|
|
30892
|
-
|
|
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
|
+
}]
|
|
30893
31556
|
};
|
|
31557
|
+
} catch (error2) {
|
|
31558
|
+
return {
|
|
31559
|
+
content: [{
|
|
31560
|
+
type: "text",
|
|
31561
|
+
text: JSON.stringify({
|
|
31562
|
+
success: false,
|
|
31563
|
+
error: error2 instanceof Error ? error2.message : "Unknown error",
|
|
31564
|
+
id: params.id
|
|
31565
|
+
})
|
|
31566
|
+
}],
|
|
31567
|
+
isError: true
|
|
31568
|
+
};
|
|
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++;
|
|
30894
31651
|
}
|
|
30895
31652
|
return {
|
|
30896
31653
|
content: [{
|
|
30897
31654
|
type: "text",
|
|
30898
|
-
text:
|
|
30899
|
-
|
|
30900
|
-
|
|
30901
|
-
|
|
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
|
+
})
|
|
30902
31665
|
}]
|
|
30903
31666
|
};
|
|
30904
31667
|
}
|
|
30905
31668
|
|
|
30906
31669
|
// src/tools/library-update.ts
|
|
30907
|
-
import { existsSync as
|
|
31670
|
+
import { existsSync as existsSync5, readdirSync } from "fs";
|
|
30908
31671
|
import { readFile as readFile5 } from "fs/promises";
|
|
30909
|
-
import { homedir as
|
|
30910
|
-
import { join as
|
|
30911
|
-
var KICAD_VERSIONS = ["9.0", "8.0"];
|
|
31672
|
+
import { homedir as homedir4, platform as platform4 } from "os";
|
|
31673
|
+
import { join as join7 } from "path";
|
|
30912
31674
|
var LIBRARY_NAMESPACE3 = "jlc_mcp";
|
|
30913
|
-
function detectKicadVersion3() {
|
|
30914
|
-
const home = homedir3();
|
|
30915
|
-
const baseDir = join5(home, "Documents", "KiCad");
|
|
30916
|
-
for (const version2 of KICAD_VERSIONS) {
|
|
30917
|
-
if (existsSync4(join5(baseDir, version2))) {
|
|
30918
|
-
return version2;
|
|
30919
|
-
}
|
|
30920
|
-
}
|
|
30921
|
-
return "9.0";
|
|
30922
|
-
}
|
|
30923
31675
|
function getLibraryPaths(projectPath) {
|
|
30924
31676
|
if (projectPath) {
|
|
30925
|
-
const librariesDir =
|
|
31677
|
+
const librariesDir = join7(projectPath, "libraries");
|
|
30926
31678
|
return {
|
|
30927
|
-
symbolsDir:
|
|
30928
|
-
footprintDir:
|
|
30929
|
-
models3dDir:
|
|
31679
|
+
symbolsDir: join7(librariesDir, "symbols"),
|
|
31680
|
+
footprintDir: join7(librariesDir, "footprints", getFootprintDirName()),
|
|
31681
|
+
models3dDir: join7(librariesDir, "3dmodels", get3DModelsDirName())
|
|
30930
31682
|
};
|
|
30931
31683
|
}
|
|
30932
|
-
const home =
|
|
30933
|
-
const version2 =
|
|
30934
|
-
const plat =
|
|
31684
|
+
const home = homedir4();
|
|
31685
|
+
const version2 = detectKicadVersion();
|
|
31686
|
+
const plat = platform4();
|
|
30935
31687
|
let base;
|
|
30936
31688
|
if (plat === "linux") {
|
|
30937
|
-
base =
|
|
31689
|
+
base = join7(home, ".local", "share", "kicad", version2, "3rdparty", LIBRARY_NAMESPACE3);
|
|
30938
31690
|
} else {
|
|
30939
|
-
base =
|
|
31691
|
+
base = join7(home, "Documents", "KiCad", version2, "3rdparty", LIBRARY_NAMESPACE3);
|
|
30940
31692
|
}
|
|
30941
31693
|
return {
|
|
30942
|
-
symbolsDir:
|
|
30943
|
-
footprintDir:
|
|
30944
|
-
models3dDir:
|
|
31694
|
+
symbolsDir: join7(base, "symbols"),
|
|
31695
|
+
footprintDir: join7(base, "footprints", getFootprintDirName()),
|
|
31696
|
+
models3dDir: join7(base, "3dmodels", get3DModelsDirName())
|
|
30945
31697
|
};
|
|
30946
31698
|
}
|
|
30947
31699
|
function extractLcscIdsFromLibrary(content) {
|
|
@@ -30962,12 +31714,12 @@ function generateEmptyLibrary() {
|
|
|
30962
31714
|
`;
|
|
30963
31715
|
}
|
|
30964
31716
|
function findJlcLibraries(symbolsDir) {
|
|
30965
|
-
if (!
|
|
31717
|
+
if (!existsSync5(symbolsDir)) {
|
|
30966
31718
|
return [];
|
|
30967
31719
|
}
|
|
30968
31720
|
try {
|
|
30969
31721
|
const files = readdirSync(symbolsDir);
|
|
30970
|
-
return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) =>
|
|
31722
|
+
return files.filter((f) => f.startsWith("JLC-MCP-") && f.endsWith(".kicad_sym")).map((f) => join7(symbolsDir, f));
|
|
30971
31723
|
} catch {
|
|
30972
31724
|
return [];
|
|
30973
31725
|
}
|
|
@@ -31034,7 +31786,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31034
31786
|
libraries_to_create: allCategories.map((cat) => ({
|
|
31035
31787
|
category: cat,
|
|
31036
31788
|
filename: getLibraryFilename(cat),
|
|
31037
|
-
path:
|
|
31789
|
+
path: join7(paths.symbolsDir, getLibraryFilename(cat))
|
|
31038
31790
|
}))
|
|
31039
31791
|
}, null, 2)
|
|
31040
31792
|
}]
|
|
@@ -31047,7 +31799,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31047
31799
|
const createdLibraries = [];
|
|
31048
31800
|
for (const category of allCategories) {
|
|
31049
31801
|
const filename = getLibraryFilename(category);
|
|
31050
|
-
const filepath =
|
|
31802
|
+
const filepath = join7(paths.symbolsDir, filename);
|
|
31051
31803
|
await writeText(filepath, emptyContent);
|
|
31052
31804
|
createdLibraries.push(filepath);
|
|
31053
31805
|
}
|
|
@@ -31124,7 +31876,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31124
31876
|
if (!params.dry_run && footprintResult.type === "generated") {
|
|
31125
31877
|
await ensureDir(paths.footprintDir);
|
|
31126
31878
|
const footprintName = footprintResult.name + "_" + lcscId;
|
|
31127
|
-
const footprintPath =
|
|
31879
|
+
const footprintPath = join7(paths.footprintDir, `${footprintName}.kicad_mod`);
|
|
31128
31880
|
await writeText(footprintPath, footprintResult.content);
|
|
31129
31881
|
}
|
|
31130
31882
|
results.push({
|
|
@@ -31151,7 +31903,7 @@ async function handleUpdateLibrary(args) {
|
|
|
31151
31903
|
await ensureDir(paths.symbolsDir);
|
|
31152
31904
|
for (const [category, entries] of categorySymbols) {
|
|
31153
31905
|
const filename = getLibraryFilename(category);
|
|
31154
|
-
const filepath =
|
|
31906
|
+
const filepath = join7(paths.symbolsDir, filename);
|
|
31155
31907
|
const header = `(kicad_symbol_lib
|
|
31156
31908
|
(version 20241209)
|
|
31157
31909
|
(generator "jlc-mcp")
|
|
@@ -31179,53 +31931,41 @@ async function handleUpdateLibrary(args) {
|
|
|
31179
31931
|
success: true,
|
|
31180
31932
|
dry_run: params.dry_run,
|
|
31181
31933
|
summary: {
|
|
31182
|
-
|
|
31934
|
+
total: allLcscIds.size,
|
|
31183
31935
|
updated: successful.length,
|
|
31184
|
-
failed: failed.length
|
|
31185
|
-
libraries_generated: categorySymbols.size
|
|
31936
|
+
failed: failed.length
|
|
31186
31937
|
},
|
|
31187
31938
|
by_category: Object.fromEntries(byCategory),
|
|
31188
31939
|
footprint_stats: footprintStats,
|
|
31189
|
-
|
|
31190
|
-
failed_components: failed.map((f) => ({
|
|
31940
|
+
failed_sample: failed.slice(0, 5).map((f) => ({
|
|
31191
31941
|
lcsc_id: f.lcscId,
|
|
31192
31942
|
error: f.error
|
|
31193
|
-
}))
|
|
31194
|
-
|
|
31943
|
+
})),
|
|
31944
|
+
has_more_failures: failed.length > 5
|
|
31945
|
+
})
|
|
31195
31946
|
}]
|
|
31196
31947
|
};
|
|
31197
31948
|
}
|
|
31198
31949
|
|
|
31199
31950
|
// src/tools/library-fix.ts
|
|
31200
|
-
import { existsSync as
|
|
31951
|
+
import { existsSync as existsSync7 } from "fs";
|
|
31201
31952
|
import { readFile as readFile7 } from "fs/promises";
|
|
31202
|
-
import { homedir as
|
|
31203
|
-
import { join as
|
|
31204
|
-
var KICAD_VERSIONS4 = ["9.0", "8.0"];
|
|
31205
|
-
function detectKicadVersion5() {
|
|
31206
|
-
const home = homedir5();
|
|
31207
|
-
const baseDir = join7(home, "Documents", "KiCad");
|
|
31208
|
-
for (const version2 of KICAD_VERSIONS4) {
|
|
31209
|
-
if (existsSync6(join7(baseDir, version2))) {
|
|
31210
|
-
return version2;
|
|
31211
|
-
}
|
|
31212
|
-
}
|
|
31213
|
-
return "9.0";
|
|
31214
|
-
}
|
|
31953
|
+
import { homedir as homedir6 } from "os";
|
|
31954
|
+
import { join as join9 } from "path";
|
|
31215
31955
|
function getLibraryPaths2(projectPath) {
|
|
31216
31956
|
if (projectPath) {
|
|
31217
|
-
const librariesDir =
|
|
31957
|
+
const librariesDir = join9(projectPath, "libraries");
|
|
31218
31958
|
return {
|
|
31219
|
-
symbolsDir:
|
|
31220
|
-
footprintDir:
|
|
31959
|
+
symbolsDir: join9(librariesDir, "symbols"),
|
|
31960
|
+
footprintDir: join9(librariesDir, "footprints", getFootprintDirName())
|
|
31221
31961
|
};
|
|
31222
31962
|
}
|
|
31223
|
-
const home =
|
|
31224
|
-
const version2 =
|
|
31225
|
-
const base =
|
|
31963
|
+
const home = homedir6();
|
|
31964
|
+
const version2 = detectKicadVersion();
|
|
31965
|
+
const base = join9(home, "Documents", "KiCad", version2);
|
|
31226
31966
|
return {
|
|
31227
|
-
symbolsDir:
|
|
31228
|
-
footprintDir:
|
|
31967
|
+
symbolsDir: join9(base, "symbols"),
|
|
31968
|
+
footprintDir: join9(base, "footprints", getFootprintDirName())
|
|
31229
31969
|
};
|
|
31230
31970
|
}
|
|
31231
31971
|
var PinElectricalType = exports_external.enum([
|
|
@@ -31393,8 +32133,8 @@ async function handleFixLibrary(args) {
|
|
|
31393
32133
|
}
|
|
31394
32134
|
const category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
|
|
31395
32135
|
const symbolLibraryFilename = getLibraryFilename(category);
|
|
31396
|
-
const symbolFile =
|
|
31397
|
-
if (!
|
|
32136
|
+
const symbolFile = join9(paths.symbolsDir, symbolLibraryFilename);
|
|
32137
|
+
if (!existsSync7(symbolFile) && !params.force) {
|
|
31398
32138
|
return {
|
|
31399
32139
|
content: [{
|
|
31400
32140
|
type: "text",
|
|
@@ -31408,7 +32148,7 @@ async function handleFixLibrary(args) {
|
|
|
31408
32148
|
isError: true
|
|
31409
32149
|
};
|
|
31410
32150
|
}
|
|
31411
|
-
if (
|
|
32151
|
+
if (existsSync7(symbolFile) && !params.force) {
|
|
31412
32152
|
const existingContent = await readFile7(symbolFile, "utf-8");
|
|
31413
32153
|
if (!symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
|
|
31414
32154
|
return {
|
|
@@ -31495,14 +32235,14 @@ async function handleFixLibrary(args) {
|
|
|
31495
32235
|
const footprintName = footprintResult.name + "_" + params.lcsc_id;
|
|
31496
32236
|
footprintRef = getFootprintReference2(footprintName);
|
|
31497
32237
|
await ensureDir(paths.footprintDir);
|
|
31498
|
-
const footprintPath =
|
|
32238
|
+
const footprintPath = join9(paths.footprintDir, `${footprintName}.kicad_mod`);
|
|
31499
32239
|
await writeText(footprintPath, footprintResult.content);
|
|
31500
32240
|
}
|
|
31501
32241
|
component.info.package = footprintRef;
|
|
31502
32242
|
await ensureDir(paths.symbolsDir);
|
|
31503
32243
|
let symbolContent;
|
|
31504
32244
|
let symbolAction;
|
|
31505
|
-
if (
|
|
32245
|
+
if (existsSync7(symbolFile)) {
|
|
31506
32246
|
const existingContent = await readFile7(symbolFile, "utf-8");
|
|
31507
32247
|
symbolContent = symbolConverter.replaceInLibrary(existingContent, component);
|
|
31508
32248
|
symbolAction = "replaced";
|
|
@@ -31531,711 +32271,43 @@ async function handleFixLibrary(args) {
|
|
|
31531
32271
|
};
|
|
31532
32272
|
}
|
|
31533
32273
|
|
|
31534
|
-
// src/tools/easyeda.ts
|
|
31535
|
-
import { join as join8 } from "path";
|
|
31536
|
-
import { execSync as execSync2 } from "child_process";
|
|
31537
|
-
import { tmpdir } from "os";
|
|
31538
|
-
var easyedaSearchTool = {
|
|
31539
|
-
name: "easyeda_search",
|
|
31540
|
-
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.",
|
|
31541
|
-
inputSchema: {
|
|
31542
|
-
type: "object",
|
|
31543
|
-
properties: {
|
|
31544
|
-
query: {
|
|
31545
|
-
type: "string",
|
|
31546
|
-
description: 'Search term (e.g., "XIAO RP2040", "ESP32-C3 module")'
|
|
31547
|
-
},
|
|
31548
|
-
source: {
|
|
31549
|
-
type: "string",
|
|
31550
|
-
enum: ["user", "lcsc", "easyeda", "all"],
|
|
31551
|
-
description: 'Filter by source. "user" for community-contributed (default)'
|
|
31552
|
-
},
|
|
31553
|
-
limit: {
|
|
31554
|
-
type: "number",
|
|
31555
|
-
description: "Max results to return (default: 20)"
|
|
31556
|
-
},
|
|
31557
|
-
open_preview: {
|
|
31558
|
-
type: "boolean",
|
|
31559
|
-
description: "Generate and open HTML preview in browser",
|
|
31560
|
-
default: true
|
|
31561
|
-
}
|
|
31562
|
-
},
|
|
31563
|
-
required: ["query"]
|
|
31564
|
-
}
|
|
31565
|
-
};
|
|
31566
|
-
var easyedaGet3DModelTool = {
|
|
31567
|
-
name: "easyeda_get_3d_model",
|
|
31568
|
-
description: "Download 3D model for an EasyEDA community component. Requires the model UUID from easyeda_get.",
|
|
31569
|
-
inputSchema: {
|
|
31570
|
-
type: "object",
|
|
31571
|
-
properties: {
|
|
31572
|
-
uuid: {
|
|
31573
|
-
type: "string",
|
|
31574
|
-
description: "3D model UUID from easyeda_get result"
|
|
31575
|
-
},
|
|
31576
|
-
format: {
|
|
31577
|
-
type: "string",
|
|
31578
|
-
enum: ["step", "obj"],
|
|
31579
|
-
description: 'Model format: "step" or "obj" (default: step)'
|
|
31580
|
-
}
|
|
31581
|
-
},
|
|
31582
|
-
required: ["uuid"]
|
|
31583
|
-
}
|
|
31584
|
-
};
|
|
31585
|
-
var EasyedaSearchParamsSchema = exports_external.object({
|
|
31586
|
-
query: exports_external.string().min(1),
|
|
31587
|
-
source: exports_external.enum(["user", "lcsc", "easyeda", "all"]).optional(),
|
|
31588
|
-
limit: exports_external.number().min(1).max(100).optional(),
|
|
31589
|
-
open_preview: exports_external.boolean().optional()
|
|
31590
|
-
});
|
|
31591
|
-
var EasyedaGet3DModelParamsSchema = exports_external.object({
|
|
31592
|
-
uuid: exports_external.string().min(1),
|
|
31593
|
-
format: exports_external.enum(["step", "obj"]).default("step")
|
|
31594
|
-
});
|
|
31595
|
-
async function handleEasyedaSearch(args) {
|
|
31596
|
-
const params = EasyedaSearchParamsSchema.parse(args);
|
|
31597
|
-
const openPreview = params.open_preview ?? true;
|
|
31598
|
-
const results = await easyedaCommunityClient.search({
|
|
31599
|
-
query: params.query,
|
|
31600
|
-
source: params.source,
|
|
31601
|
-
limit: params.limit || 20
|
|
31602
|
-
});
|
|
31603
|
-
if (results.length === 0) {
|
|
31604
|
-
return {
|
|
31605
|
-
content: [
|
|
31606
|
-
{
|
|
31607
|
-
type: "text",
|
|
31608
|
-
text: `No results found for "${params.query}"`
|
|
31609
|
-
}
|
|
31610
|
-
]
|
|
31611
|
-
};
|
|
31612
|
-
}
|
|
31613
|
-
let output = `Found ${results.length} results for "${params.query}":
|
|
31614
|
-
|
|
31615
|
-
`;
|
|
31616
|
-
output += `| # | Title | Package | Owner | UUID |
|
|
31617
|
-
`;
|
|
31618
|
-
output += `|---|-------|---------|-------|------|
|
|
31619
|
-
`;
|
|
31620
|
-
for (let i = 0;i < results.length; i++) {
|
|
31621
|
-
const r = results[i];
|
|
31622
|
-
output += `| ${i + 1} | ${r.title} | ${r.package} | ${r.owner.nickname || r.owner.username} | ${r.uuid} |
|
|
31623
|
-
`;
|
|
31624
|
-
}
|
|
31625
|
-
output += "\nUse `library_fetch` with the UUID to add component to global JLC-MCP libraries.";
|
|
31626
|
-
output += "\nUse `easyeda_fetch` with the UUID to add to project-local EasyEDA library.";
|
|
31627
|
-
if (openPreview) {
|
|
31628
|
-
const { filepath, browserOpened } = await generateHtmlPreview(params.query, results);
|
|
31629
|
-
if (browserOpened) {
|
|
31630
|
-
output += `
|
|
31631
|
-
|
|
31632
|
-
HTML preview opened in browser.`;
|
|
31633
|
-
} else {
|
|
31634
|
-
output += `
|
|
31635
|
-
|
|
31636
|
-
Could not open browser automatically.`;
|
|
31637
|
-
}
|
|
31638
|
-
output += `
|
|
31639
|
-
Preview file: ${filepath}`;
|
|
31640
|
-
}
|
|
31641
|
-
return {
|
|
31642
|
-
content: [
|
|
31643
|
-
{
|
|
31644
|
-
type: "text",
|
|
31645
|
-
text: output
|
|
31646
|
-
}
|
|
31647
|
-
]
|
|
31648
|
-
};
|
|
31649
|
-
}
|
|
31650
|
-
async function handleEasyedaGet3DModel(args) {
|
|
31651
|
-
const params = EasyedaGet3DModelParamsSchema.parse(args);
|
|
31652
|
-
const model = await easyedaCommunityClient.get3DModel(params.uuid, params.format);
|
|
31653
|
-
if (!model) {
|
|
31654
|
-
return {
|
|
31655
|
-
content: [
|
|
31656
|
-
{
|
|
31657
|
-
type: "text",
|
|
31658
|
-
text: `3D model ${params.uuid} not found`
|
|
31659
|
-
}
|
|
31660
|
-
],
|
|
31661
|
-
isError: true
|
|
31662
|
-
};
|
|
31663
|
-
}
|
|
31664
|
-
return {
|
|
31665
|
-
content: [
|
|
31666
|
-
{
|
|
31667
|
-
type: "text",
|
|
31668
|
-
text: `3D model downloaded (${model.length} bytes, ${params.format.toUpperCase()} format)
|
|
31669
|
-
|
|
31670
|
-
Base64 data:
|
|
31671
|
-
${model.toString("base64").slice(0, 500)}...`
|
|
31672
|
-
}
|
|
31673
|
-
]
|
|
31674
|
-
};
|
|
31675
|
-
}
|
|
31676
|
-
async function generateHtmlPreview(query, results) {
|
|
31677
|
-
const timestamp = Date.now();
|
|
31678
|
-
const filename = `easyeda-search-${timestamp}.html`;
|
|
31679
|
-
const filepath = join8(tmpdir(), filename);
|
|
31680
|
-
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
|
-
const noImageDataUri = `data:image/svg+xml,${encodeURIComponent(noImageSvg)}`;
|
|
31682
|
-
const cardsPromises = results.slice(0, 10).map(async (r) => {
|
|
31683
|
-
const symbolImageUrl = `https://image.easyeda.com/components/${r.uuid}.png`;
|
|
31684
|
-
let footprintSvg = "";
|
|
31685
|
-
try {
|
|
31686
|
-
const component = await easyedaCommunityClient.getComponent(r.uuid);
|
|
31687
|
-
if (component) {
|
|
31688
|
-
const rawData = component.rawData;
|
|
31689
|
-
const fpDataStr = rawData?.packageDetail?.dataStr || rawData?.dataStr;
|
|
31690
|
-
if (fpDataStr?.shape) {
|
|
31691
|
-
footprintSvg = generateFootprintSvg(fpDataStr);
|
|
31692
|
-
}
|
|
31693
|
-
}
|
|
31694
|
-
} catch {}
|
|
31695
|
-
const footprintDataUri = footprintSvg ? `data:image/svg+xml,${encodeURIComponent(footprintSvg)}` : noImageDataUri;
|
|
31696
|
-
return `
|
|
31697
|
-
<div class="card">
|
|
31698
|
-
<div class="images">
|
|
31699
|
-
<div class="image-box">
|
|
31700
|
-
<div class="image-label">Symbol</div>
|
|
31701
|
-
<img src="${symbolImageUrl}" alt="Symbol" onerror="this.src='${noImageDataUri}'">
|
|
31702
|
-
</div>
|
|
31703
|
-
<div class="image-box">
|
|
31704
|
-
<div class="image-label">Footprint</div>
|
|
31705
|
-
<img src="${footprintDataUri}" alt="Footprint">
|
|
31706
|
-
</div>
|
|
31707
|
-
</div>
|
|
31708
|
-
<h3>${escapeHtml(r.title)}</h3>
|
|
31709
|
-
<div class="meta">
|
|
31710
|
-
<div><strong>Package:</strong> ${escapeHtml(r.package || "N/A")}</div>
|
|
31711
|
-
<div><strong>Owner:</strong> ${escapeHtml(r.owner.nickname || r.owner.username)}</div>
|
|
31712
|
-
${r.manufacturer ? `<div><strong>Mfr:</strong> ${escapeHtml(r.manufacturer)}</div>` : ""}
|
|
31713
|
-
</div>
|
|
31714
|
-
<div class="uuid" onclick="navigator.clipboard.writeText('${r.uuid}'); this.classList.add('copied'); setTimeout(() => this.classList.remove('copied'), 1000);">
|
|
31715
|
-
${r.uuid}
|
|
31716
|
-
</div>
|
|
31717
|
-
</div>`;
|
|
31718
|
-
});
|
|
31719
|
-
const cards = (await Promise.all(cardsPromises)).join(`
|
|
31720
|
-
`);
|
|
31721
|
-
const html = `<!DOCTYPE html>
|
|
31722
|
-
<html lang="en">
|
|
31723
|
-
<head>
|
|
31724
|
-
<meta charset="UTF-8">
|
|
31725
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31726
|
-
<title>EasyEDA Search: ${escapeHtml(query)}</title>
|
|
31727
|
-
<style>
|
|
31728
|
-
* { box-sizing: border-box; }
|
|
31729
|
-
body {
|
|
31730
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
31731
|
-
max-width: 1400px;
|
|
31732
|
-
margin: 0 auto;
|
|
31733
|
-
padding: 20px;
|
|
31734
|
-
background: #f9f9f9;
|
|
31735
|
-
color: #333;
|
|
31736
|
-
}
|
|
31737
|
-
h1 { margin-bottom: 8px; }
|
|
31738
|
-
.subtitle { color: #666; margin-bottom: 20px; }
|
|
31739
|
-
.grid {
|
|
31740
|
-
display: grid;
|
|
31741
|
-
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
31742
|
-
gap: 16px;
|
|
31743
|
-
}
|
|
31744
|
-
.card {
|
|
31745
|
-
background: white;
|
|
31746
|
-
border: 1px solid #ddd;
|
|
31747
|
-
border-radius: 8px;
|
|
31748
|
-
padding: 16px;
|
|
31749
|
-
transition: box-shadow 0.2s;
|
|
31750
|
-
}
|
|
31751
|
-
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
31752
|
-
.card .images {
|
|
31753
|
-
display: flex;
|
|
31754
|
-
gap: 8px;
|
|
31755
|
-
margin-bottom: 12px;
|
|
31756
|
-
}
|
|
31757
|
-
.card .image-box {
|
|
31758
|
-
flex: 1;
|
|
31759
|
-
min-width: 0;
|
|
31760
|
-
}
|
|
31761
|
-
.card .image-label {
|
|
31762
|
-
font-size: 10px;
|
|
31763
|
-
color: #888;
|
|
31764
|
-
text-transform: uppercase;
|
|
31765
|
-
text-align: center;
|
|
31766
|
-
margin-bottom: 4px;
|
|
31767
|
-
}
|
|
31768
|
-
.card img {
|
|
31769
|
-
width: 100%;
|
|
31770
|
-
height: 120px;
|
|
31771
|
-
object-fit: contain;
|
|
31772
|
-
border-radius: 4px;
|
|
31773
|
-
border: 1px solid #eee;
|
|
31774
|
-
}
|
|
31775
|
-
.card h3 {
|
|
31776
|
-
margin: 0 0 8px;
|
|
31777
|
-
font-size: 15px;
|
|
31778
|
-
line-height: 1.3;
|
|
31779
|
-
overflow: hidden;
|
|
31780
|
-
text-overflow: ellipsis;
|
|
31781
|
-
white-space: nowrap;
|
|
31782
|
-
}
|
|
31783
|
-
.card .meta {
|
|
31784
|
-
color: #666;
|
|
31785
|
-
font-size: 12px;
|
|
31786
|
-
line-height: 1.6;
|
|
31787
|
-
}
|
|
31788
|
-
.card .meta div { margin-bottom: 2px; }
|
|
31789
|
-
.card .uuid {
|
|
31790
|
-
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
31791
|
-
font-size: 10px;
|
|
31792
|
-
color: #888;
|
|
31793
|
-
background: #f5f5f5;
|
|
31794
|
-
padding: 6px 8px;
|
|
31795
|
-
border-radius: 4px;
|
|
31796
|
-
margin-top: 12px;
|
|
31797
|
-
cursor: pointer;
|
|
31798
|
-
word-break: break-all;
|
|
31799
|
-
transition: background 0.2s;
|
|
31800
|
-
}
|
|
31801
|
-
.card .uuid:hover { background: #e8e8e8; }
|
|
31802
|
-
.card .uuid.copied { background: #d4edda; color: #155724; }
|
|
31803
|
-
.instructions {
|
|
31804
|
-
background: #e8f4fd;
|
|
31805
|
-
border: 1px solid #b8daff;
|
|
31806
|
-
border-radius: 8px;
|
|
31807
|
-
padding: 16px;
|
|
31808
|
-
margin-bottom: 20px;
|
|
31809
|
-
font-size: 14px;
|
|
31810
|
-
}
|
|
31811
|
-
.instructions code {
|
|
31812
|
-
background: #fff;
|
|
31813
|
-
padding: 2px 6px;
|
|
31814
|
-
border-radius: 4px;
|
|
31815
|
-
font-family: 'SF Mono', Monaco, monospace;
|
|
31816
|
-
}
|
|
31817
|
-
</style>
|
|
31818
|
-
</head>
|
|
31819
|
-
<body>
|
|
31820
|
-
<h1>EasyEDA Search: "${escapeHtml(query)}"</h1>
|
|
31821
|
-
<p class="subtitle">Found ${results.length} results. Click UUID to copy to clipboard.</p>
|
|
31822
|
-
|
|
31823
|
-
<div class="instructions">
|
|
31824
|
-
<strong>How to use:</strong><br>
|
|
31825
|
-
1. Click on a UUID to copy it<br>
|
|
31826
|
-
2. Use <code>library_fetch</code> with the UUID to add to global JLC-MCP libraries<br>
|
|
31827
|
-
3. Or use <code>easyeda_fetch</code> for project-local EasyEDA library
|
|
31828
|
-
</div>
|
|
31829
|
-
|
|
31830
|
-
<div class="grid">
|
|
31831
|
-
${cards}
|
|
31832
|
-
</div>
|
|
31833
|
-
</body>
|
|
31834
|
-
</html>`;
|
|
31835
|
-
__require("fs").writeFileSync(filepath, html, "utf-8");
|
|
31836
|
-
const browserOpened = openInBrowser(filepath);
|
|
31837
|
-
return { filepath, browserOpened };
|
|
31838
|
-
}
|
|
31839
|
-
function openInBrowser(filepath) {
|
|
31840
|
-
const platform4 = process.platform;
|
|
31841
|
-
try {
|
|
31842
|
-
switch (platform4) {
|
|
31843
|
-
case "darwin":
|
|
31844
|
-
execSync2(`open "${filepath}"`, { stdio: "ignore" });
|
|
31845
|
-
return true;
|
|
31846
|
-
case "win32":
|
|
31847
|
-
execSync2(`start "" "${filepath}"`, { stdio: "ignore", shell: "cmd.exe" });
|
|
31848
|
-
return true;
|
|
31849
|
-
case "linux":
|
|
31850
|
-
default:
|
|
31851
|
-
execSync2(`xdg-open "${filepath}"`, { stdio: "ignore" });
|
|
31852
|
-
return true;
|
|
31853
|
-
}
|
|
31854
|
-
} catch {
|
|
31855
|
-
const fallbacks = ["xdg-open", "sensible-browser", "x-www-browser", "gnome-open"];
|
|
31856
|
-
for (const cmd of fallbacks) {
|
|
31857
|
-
try {
|
|
31858
|
-
execSync2(`${cmd} "${filepath}"`, { stdio: "ignore" });
|
|
31859
|
-
return true;
|
|
31860
|
-
} catch {}
|
|
31861
|
-
}
|
|
31862
|
-
return false;
|
|
31863
|
-
}
|
|
31864
|
-
}
|
|
31865
|
-
function escapeHtml(str) {
|
|
31866
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
31867
|
-
}
|
|
31868
|
-
function generateFootprintSvg(dataStr) {
|
|
31869
|
-
if (!dataStr.shape || dataStr.shape.length === 0) {
|
|
31870
|
-
return "";
|
|
31871
|
-
}
|
|
31872
|
-
const bbox = dataStr.BBox || { x: 0, y: 0, width: 100, height: 100 };
|
|
31873
|
-
const padding = 5;
|
|
31874
|
-
const viewBox = `${bbox.x - padding} ${bbox.y - padding} ${bbox.width + padding * 2} ${bbox.height + padding * 2}`;
|
|
31875
|
-
const regions = [];
|
|
31876
|
-
const tracks = [];
|
|
31877
|
-
const pads = [];
|
|
31878
|
-
const holes = [];
|
|
31879
|
-
const texts = [];
|
|
31880
|
-
for (const shape of dataStr.shape) {
|
|
31881
|
-
if (typeof shape !== "string")
|
|
31882
|
-
continue;
|
|
31883
|
-
if (shape.startsWith("SOLIDREGION~")) {
|
|
31884
|
-
const svg = renderSolidRegion(shape);
|
|
31885
|
-
if (svg)
|
|
31886
|
-
regions.push(svg);
|
|
31887
|
-
} else if (shape.startsWith("TRACK~")) {
|
|
31888
|
-
const svg = renderTrackShape(shape);
|
|
31889
|
-
if (svg)
|
|
31890
|
-
tracks.push(svg);
|
|
31891
|
-
} else if (shape.startsWith("PAD~")) {
|
|
31892
|
-
const result = renderPadShape(shape);
|
|
31893
|
-
if (result) {
|
|
31894
|
-
pads.push(result.pad);
|
|
31895
|
-
if (result.hole)
|
|
31896
|
-
holes.push(result.hole);
|
|
31897
|
-
}
|
|
31898
|
-
} else if (shape.startsWith("TEXT~")) {
|
|
31899
|
-
const svg = renderTextShape(shape);
|
|
31900
|
-
if (svg)
|
|
31901
|
-
texts.push(svg);
|
|
31902
|
-
}
|
|
31903
|
-
}
|
|
31904
|
-
const allElements = [...regions, ...tracks, ...pads, ...holes, ...texts];
|
|
31905
|
-
if (allElements.length === 0) {
|
|
31906
|
-
return "";
|
|
31907
|
-
}
|
|
31908
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" style="background:#000000">
|
|
31909
|
-
<style>
|
|
31910
|
-
.pad { fill: #CC0000; stroke: none; }
|
|
31911
|
-
.pad-hole { fill: #666666; }
|
|
31912
|
-
.track { fill: none; stroke: #FFFF00; stroke-linecap: round; stroke-linejoin: round; }
|
|
31913
|
-
.region { fill: #CC0000; opacity: 0.6; }
|
|
31914
|
-
.text-path { fill: none; stroke: #FFFF00; stroke-width: 0.4; stroke-linecap: round; stroke-linejoin: round; }
|
|
31915
|
-
</style>
|
|
31916
|
-
${allElements.join(`
|
|
31917
|
-
`)}
|
|
31918
|
-
</svg>`;
|
|
31919
|
-
}
|
|
31920
|
-
function renderPadShape(padData) {
|
|
31921
|
-
const fields = padData.split("~");
|
|
31922
|
-
const shapeType = fields[1];
|
|
31923
|
-
const cx = parseFloat(fields[2]) || 0;
|
|
31924
|
-
const cy = parseFloat(fields[3]) || 0;
|
|
31925
|
-
if (shapeType === "POLYGON") {
|
|
31926
|
-
const holeDia2 = parseFloat(fields[9]) || 0;
|
|
31927
|
-
const pointsStr = fields[10] || "";
|
|
31928
|
-
if (!pointsStr)
|
|
31929
|
-
return null;
|
|
31930
|
-
const coords = pointsStr.split(" ").map(Number);
|
|
31931
|
-
if (coords.length < 4)
|
|
31932
|
-
return null;
|
|
31933
|
-
let pathD = `M ${coords[0]} ${coords[1]}`;
|
|
31934
|
-
for (let i = 2;i < coords.length; i += 2) {
|
|
31935
|
-
pathD += ` L ${coords[i]} ${coords[i + 1]}`;
|
|
31936
|
-
}
|
|
31937
|
-
pathD += " Z";
|
|
31938
|
-
return {
|
|
31939
|
-
pad: `<path class="pad" d="${pathD}"/>`,
|
|
31940
|
-
hole: holeDia2 > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia2}"/>` : null
|
|
31941
|
-
};
|
|
31942
|
-
}
|
|
31943
|
-
const width = parseFloat(fields[4]) || 0;
|
|
31944
|
-
const height = parseFloat(fields[5]) || 0;
|
|
31945
|
-
const holeDia = parseFloat(fields[9]) || 0;
|
|
31946
|
-
let padSvg = "";
|
|
31947
|
-
if (shapeType === "ELLIPSE" || shapeType === "OVAL" || shapeType === "ROUND") {
|
|
31948
|
-
const rx = width / 2;
|
|
31949
|
-
const ry = height / 2;
|
|
31950
|
-
padSvg = `<ellipse class="pad" cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}"/>`;
|
|
31951
|
-
} else {
|
|
31952
|
-
const rectX = cx - width / 2;
|
|
31953
|
-
const rectY = cy - height / 2;
|
|
31954
|
-
padSvg = `<rect class="pad" x="${rectX}" y="${rectY}" width="${width}" height="${height}"/>`;
|
|
31955
|
-
}
|
|
31956
|
-
return {
|
|
31957
|
-
pad: padSvg,
|
|
31958
|
-
hole: holeDia > 0 ? `<circle class="pad-hole" cx="${cx}" cy="${cy}" r="${holeDia}"/>` : null
|
|
31959
|
-
};
|
|
31960
|
-
}
|
|
31961
|
-
function renderTrackShape(trackData) {
|
|
31962
|
-
const fields = trackData.split("~");
|
|
31963
|
-
const strokeWidth = parseFloat(fields[1]) || 0.5;
|
|
31964
|
-
const pointsStr = fields[4] || "";
|
|
31965
|
-
if (!pointsStr)
|
|
31966
|
-
return null;
|
|
31967
|
-
const coords = pointsStr.split(" ").map(Number);
|
|
31968
|
-
if (coords.length < 4)
|
|
31969
|
-
return null;
|
|
31970
|
-
let pathD = `M ${coords[0]} ${coords[1]}`;
|
|
31971
|
-
for (let i = 2;i < coords.length; i += 2) {
|
|
31972
|
-
pathD += ` L ${coords[i]} ${coords[i + 1]}`;
|
|
31973
|
-
}
|
|
31974
|
-
return `<path class="track" d="${pathD}" stroke-width="${strokeWidth}"/>`;
|
|
31975
|
-
}
|
|
31976
|
-
function renderSolidRegion(regionData) {
|
|
31977
|
-
const fields = regionData.split("~");
|
|
31978
|
-
const pathD = fields[3] || "";
|
|
31979
|
-
if (!pathD || !pathD.startsWith("M"))
|
|
31980
|
-
return null;
|
|
31981
|
-
return `<path class="region" d="${pathD}"/>`;
|
|
31982
|
-
}
|
|
31983
|
-
function renderTextShape(textData) {
|
|
31984
|
-
const fields = textData.split("~");
|
|
31985
|
-
const svgPath = fields[11] || "";
|
|
31986
|
-
if (!svgPath || !svgPath.startsWith("M"))
|
|
31987
|
-
return null;
|
|
31988
|
-
return `<path class="text-path" d="${svgPath}"/>`;
|
|
31989
|
-
}
|
|
31990
|
-
|
|
31991
32274
|
// src/tools/index.ts
|
|
31992
32275
|
var tools = [
|
|
31993
|
-
|
|
31994
|
-
|
|
31995
|
-
|
|
31996
|
-
|
|
31997
|
-
fetchLibraryTool,
|
|
32276
|
+
componentSearchTool,
|
|
32277
|
+
libraryInstallTool,
|
|
32278
|
+
libraryBatchInstallTool,
|
|
32279
|
+
libraryGetComponentTool,
|
|
31998
32280
|
updateLibraryTool,
|
|
31999
|
-
fixLibraryTool
|
|
32000
|
-
get3DModelTool,
|
|
32001
|
-
easyedaSearchTool,
|
|
32002
|
-
easyedaGet3DModelTool
|
|
32281
|
+
fixLibraryTool
|
|
32003
32282
|
];
|
|
32004
32283
|
var toolHandlers = {
|
|
32005
|
-
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
|
|
32009
|
-
|
|
32010
|
-
|
|
32011
|
-
library_fix: handleFixLibrary,
|
|
32012
|
-
library_get_3d_model: handleGet3DModel,
|
|
32013
|
-
easyeda_search: handleEasyedaSearch,
|
|
32014
|
-
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
|
|
32015
32290
|
};
|
|
32016
32291
|
|
|
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
32292
|
// src/index.ts
|
|
32223
|
-
var
|
|
32293
|
+
var require2 = createRequire(import.meta.url);
|
|
32294
|
+
var { version: version2 } = require2("../package.json");
|
|
32295
|
+
var logger2 = createLogger("jlc-mcp");
|
|
32224
32296
|
var server = new Server({
|
|
32225
32297
|
name: "jlc-mcp",
|
|
32226
|
-
version:
|
|
32298
|
+
version: version2
|
|
32227
32299
|
}, {
|
|
32228
32300
|
capabilities: {
|
|
32229
32301
|
tools: {}
|
|
32230
32302
|
}
|
|
32231
32303
|
});
|
|
32232
32304
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
32233
|
-
|
|
32305
|
+
logger2.debug("Listing tools");
|
|
32234
32306
|
return { tools };
|
|
32235
32307
|
});
|
|
32236
32308
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
32237
32309
|
const { name, arguments: args } = request.params;
|
|
32238
|
-
|
|
32310
|
+
logger2.debug(`Tool call: ${name}`, args);
|
|
32239
32311
|
const handler = toolHandlers[name];
|
|
32240
32312
|
if (!handler) {
|
|
32241
32313
|
return {
|
|
@@ -32246,7 +32318,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32246
32318
|
try {
|
|
32247
32319
|
return await handler(args);
|
|
32248
32320
|
} catch (error2) {
|
|
32249
|
-
|
|
32321
|
+
logger2.error(`Tool error: ${name}`, error2);
|
|
32250
32322
|
return {
|
|
32251
32323
|
content: [{
|
|
32252
32324
|
type: "text",
|
|
@@ -32259,26 +32331,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
32259
32331
|
async function main() {
|
|
32260
32332
|
const registration = await ensureGlobalLibraryTables();
|
|
32261
32333
|
if (!registration.success) {
|
|
32262
|
-
|
|
32334
|
+
logger2.error("Failed to register JLC libraries in KiCad global tables", {
|
|
32263
32335
|
errors: registration.errors
|
|
32264
32336
|
});
|
|
32265
32337
|
process.exit(1);
|
|
32266
32338
|
}
|
|
32267
|
-
const { symLibTable, fpLibTable, libraryStubs, version:
|
|
32339
|
+
const { symLibTable, fpLibTable, libraryStubs, version: version3 } = registration;
|
|
32268
32340
|
if (symLibTable.created || symLibTable.modified || fpLibTable.created || fpLibTable.modified) {
|
|
32269
|
-
|
|
32341
|
+
logger2.info(`JLC libraries registered in KiCad ${version3}`, {
|
|
32270
32342
|
symLibTable: symLibTable.created ? `created with ${symLibTable.entriesAdded} entries` : symLibTable.modified ? `added ${symLibTable.entriesAdded} entries` : "already configured",
|
|
32271
32343
|
fpLibTable: fpLibTable.created ? "created" : fpLibTable.modified ? "updated" : "already configured",
|
|
32272
32344
|
stubsCreated: libraryStubs.symbolsCreated.length + libraryStubs.directoriesCreated.length
|
|
32273
32345
|
});
|
|
32274
32346
|
}
|
|
32275
32347
|
const httpPort = startHttpServer();
|
|
32276
|
-
|
|
32348
|
+
logger2.info(`Component browser available at http://localhost:${httpPort}`);
|
|
32277
32349
|
const transport = new StdioServerTransport;
|
|
32278
32350
|
await server.connect(transport);
|
|
32279
|
-
|
|
32351
|
+
logger2.info("JLC MCP server running on stdio");
|
|
32280
32352
|
}
|
|
32281
32353
|
main().catch((error2) => {
|
|
32282
|
-
|
|
32354
|
+
logger2.error("Server error", error2);
|
|
32283
32355
|
process.exit(1);
|
|
32284
32356
|
});
|