@jlcpcb/cli 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -861,7 +861,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
861
861
  this._exitCallback = (err) => {
862
862
  if (err.code !== "commander.executeSubCommandAsync") {
863
863
  throw err;
864
- } else {}
864
+ }
865
865
  };
866
866
  }
867
867
  return this;
@@ -9687,7 +9687,7 @@ Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fet
9687
9687
  offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
9688
9688
  wasHidden && !hoistableRoot && !prevOffscreenSubtreeIsHidden && !prevOffscreenSubtreeWasHidden && (finishedWork.mode & 2) !== NoMode && 0 <= componentEffectStartTime && 0 <= componentEffectEndTime && 0.05 < componentEffectEndTime - componentEffectStartTime && logComponentReappeared(finishedWork, componentEffectStartTime, componentEffectEndTime);
9689
9689
  commitReconciliationEffects(finishedWork);
9690
- if (flags & 8192 && (root = finishedWork.stateNode, root._visibility = hoistableRoot ? root._visibility & ~OffscreenVisible : root._visibility | OffscreenVisible, !hoistableRoot || current2 === null || wasHidden || offscreenSubtreeIsHidden || offscreenSubtreeWasHidden || (recursivelyTraverseDisappearLayoutEffects(finishedWork), (finishedWork.mode & 2) !== NoMode && 0 <= componentEffectStartTime && 0 <= componentEffectEndTime && 0.05 < componentEffectEndTime - componentEffectStartTime && logComponentTrigger(finishedWork, componentEffectStartTime, componentEffectEndTime, "Disconnect")), supportsMutation))
9690
+ if (flags & 8192 && (root = finishedWork.stateNode, root._visibility = hoistableRoot ? root._visibility & ~OffscreenVisible : root._visibility | OffscreenVisible, !hoistableRoot || current2 === null || wasHidden || offscreenSubtreeIsHidden || offscreenSubtreeWasHidden || (recursivelyTraverseDisappearLayoutEffects(finishedWork), (finishedWork.mode & 2) !== NoMode && 0 <= componentEffectStartTime && 0 <= componentEffectEndTime && 0.05 < componentEffectEndTime - componentEffectStartTime && logComponentTrigger(finishedWork, componentEffectStartTime, componentEffectEndTime, "Disconnect")), supportsMutation)) {
9691
9691
  a:
9692
9692
  if (current2 = null, supportsMutation)
9693
9693
  for (root = finishedWork;; ) {
@@ -9737,6 +9737,7 @@ Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fet
9737
9737
  root.sibling.return = root.return;
9738
9738
  root = root.sibling;
9739
9739
  }
9740
+ }
9740
9741
  flags & 4 && (flags = finishedWork.updateQueue, flags !== null && (current2 = flags.retryQueue, current2 !== null && (flags.retryQueue = null, attachSuspenseRetryListeners(finishedWork, current2))));
9741
9742
  break;
9742
9743
  case 19:
@@ -17250,7 +17251,7 @@ var require_backend = __commonJS((exports, module) => {
17250
17251
  987: (module2, __unused_webpack_exports, __webpack_require__2) => {
17251
17252
  if (true) {
17252
17253
  module2.exports = __webpack_require__2(786);
17253
- } else {}
17254
+ }
17254
17255
  },
17255
17256
  126: (__unused_webpack_module, exports2, __webpack_require__2) => {
17256
17257
  var process3 = __webpack_require__2(169);
@@ -17700,7 +17701,7 @@ var require_backend = __commonJS((exports, module) => {
17700
17701
  189: (module2, __unused_webpack_exports, __webpack_require__2) => {
17701
17702
  if (true) {
17702
17703
  module2.exports = __webpack_require__2(126);
17703
- } else {}
17704
+ }
17704
17705
  },
17705
17706
  206: function(module2, exports2, __webpack_require__2) {
17706
17707
  var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
@@ -17715,7 +17716,7 @@ var require_backend = __commonJS((exports, module) => {
17715
17716
  (function(root, factory) {
17716
17717
  if (true) {
17717
17718
  __WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__2(430)], __WEBPACK_AMD_DEFINE_FACTORY__ = factory, __WEBPACK_AMD_DEFINE_RESULT__ = typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" ? __WEBPACK_AMD_DEFINE_FACTORY__.apply(exports2, __WEBPACK_AMD_DEFINE_ARRAY__) : __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module2.exports = __WEBPACK_AMD_DEFINE_RESULT__);
17718
- } else {}
17719
+ }
17719
17720
  })(this, function ErrorStackParser(StackFrame) {
17720
17721
  var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
17721
17722
  var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
@@ -18411,7 +18412,7 @@ var require_backend = __commonJS((exports, module) => {
18411
18412
  (function(root, factory) {
18412
18413
  if (true) {
18413
18414
  __WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = factory, __WEBPACK_AMD_DEFINE_RESULT__ = typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" ? __WEBPACK_AMD_DEFINE_FACTORY__.apply(exports2, __WEBPACK_AMD_DEFINE_ARRAY__) : __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module2.exports = __WEBPACK_AMD_DEFINE_RESULT__);
18414
- } else {}
18415
+ }
18415
18416
  })(this, function() {
18416
18417
  function _isNumber(n) {
18417
18418
  return !isNaN(parseFloat(n)) && isFinite(n);
@@ -23785,7 +23786,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
23785
23786
  var suffix = "";
23786
23787
  if (true) {
23787
23788
  suffix = " (<anonymous>)";
23788
- } else {}
23789
+ }
23789
23790
  return `
23790
23791
  ` + prefix + name + suffix;
23791
23792
  }
@@ -26514,7 +26515,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
26514
26515
  hasChanges = true;
26515
26516
  updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);
26516
26517
  }
26517
- } else {}
26518
+ }
26518
26519
  }
26519
26520
  } catch (err) {
26520
26521
  _iterator2.e(err);
@@ -28293,7 +28294,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
28293
28294
  if (contentFiber === null) {
28294
28295
  throw new Error("There should always be an Offscreen Fiber child in a hydrated Suspense boundary.");
28295
28296
  }
28296
- } else {}
28297
+ }
28297
28298
  var _isTimedOut = fiber.memoizedState !== null;
28298
28299
  if (!_isTimedOut) {
28299
28300
  newSuspenseNode.rects = measureInstance(newInstance);
@@ -28327,7 +28328,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
28327
28328
  if (_contentFiber === null) {
28328
28329
  throw new Error("There should always be an Offscreen Fiber child in a hydrated Suspense boundary.");
28329
28330
  }
28330
- } else {}
28331
+ }
28331
28332
  var suspenseState = fiber.memoizedState;
28332
28333
  var _isTimedOut3 = suspenseState !== null;
28333
28334
  if (!_isTimedOut3) {
@@ -28427,7 +28428,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
28427
28428
  trackThrownPromisesFromRetryCache(newSuspenseNode, fiber.stateNode);
28428
28429
  mountSuspenseChildrenRecursively(_contentFiber2, traceNearestHostComponentUpdate, stashedSuspenseParent, stashedSuspensePrevious, stashedSuspenseRemaining);
28429
28430
  shouldPopSuspenseNode = false;
28430
- } else {}
28431
+ }
28431
28432
  } else {
28432
28433
  if (fiber.child !== null) {
28433
28434
  mountChildrenRecursively(fiber.child, traceNearestHostComponentUpdate);
@@ -29013,7 +29014,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29013
29014
  shouldPopSuspenseNode = false;
29014
29015
  } else if (previousHydrated && !nextHydrated) {
29015
29016
  throw new Error("Encountered a dehydrated Suspense boundary that was previously hydrated.");
29016
- } else {}
29017
+ }
29017
29018
  } else {
29018
29019
  if (nextFiber.child !== prevFiber.child) {
29019
29020
  updateFlags |= updateChildrenRecursively(nextFiber.child, prevFiber.child, traceNearestHostComponentUpdate);
@@ -29059,8 +29060,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29059
29060
  recordResetChildren(fiberInstance);
29060
29061
  }
29061
29062
  updateFlags &= ~ShouldResetChildren;
29062
- } else {}
29063
- } else {}
29063
+ }
29064
+ }
29064
29065
  if ((updateFlags & ShouldResetSuspenseChildren) !== NoUpdate) {
29065
29066
  if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
29066
29067
  var _suspenseNode3 = fiberInstance.suspenseNode;
@@ -29068,7 +29069,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29068
29069
  recordResetSuspenseChildren(_suspenseNode3);
29069
29070
  updateFlags &= ~ShouldResetSuspenseChildren;
29070
29071
  }
29071
- } else {}
29072
+ }
29072
29073
  }
29073
29074
  if ((updateFlags & ShouldResetParentSuspenseChildren) !== NoUpdate) {
29074
29075
  if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
@@ -29077,7 +29078,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29077
29078
  updateFlags &= ~ShouldResetParentSuspenseChildren;
29078
29079
  updateFlags |= ShouldResetSuspenseChildren;
29079
29080
  }
29080
- } else {}
29081
+ }
29081
29082
  }
29082
29083
  return updateFlags;
29083
29084
  } finally {
@@ -32141,7 +32142,7 @@ The error thrown in the component is:
32141
32142
  rendererInterface = renderer_attach(hook, id, renderer, global2, shouldStartProfilingNow, profilingSettings);
32142
32143
  } else if (renderer.ComponentTree) {
32143
32144
  rendererInterface = legacy_renderer_attach(hook, id, renderer, global2);
32144
- } else {}
32145
+ }
32145
32146
  }
32146
32147
  return rendererInterface;
32147
32148
  }
@@ -34089,6 +34090,9 @@ var {
34089
34090
  Help
34090
34091
  } = import__.default;
34091
34092
 
34093
+ // src/index.ts
34094
+ import { createRequire as createRequire2 } from "module";
34095
+
34092
34096
  // ../core/dist/index.js
34093
34097
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
34094
34098
  import { existsSync as existsSync2 } from "fs";
@@ -34102,7 +34106,7 @@ import { join, dirname, extname, basename } from "path";
34102
34106
  import { existsSync } from "fs";
34103
34107
  import { homedir, platform } from "os";
34104
34108
  import { join as join2 } from "path";
34105
- import { execSync } from "child_process";
34109
+ import { execFileSync } from "child_process";
34106
34110
  import { existsSync as existsSync4 } from "fs";
34107
34111
  import { readFile as readFile4, readdir as readdir2, unlink } from "fs/promises";
34108
34112
  import { homedir as homedir3, platform as platform3 } from "os";
@@ -39235,6 +39239,8 @@ class JLCClient {
39235
39239
  const components = data.data?.componentPageInfo?.list || [];
39236
39240
  logger3.debug(`Found ${components.length} components`);
39237
39241
  return components.map((c) => ({
39242
+ id: c.componentCode,
39243
+ idType: "lcsc",
39238
39244
  lcscId: c.componentCode,
39239
39245
  name: c.componentModelEn,
39240
39246
  manufacturer: c.componentBrandEn,
@@ -39281,6 +39287,7 @@ var jlcClient = new JLCClient;
39281
39287
  var logger4 = createLogger("http-client");
39282
39288
  async function fetchWithCurlFallback(url, options = {}) {
39283
39289
  const method = options.method || "GET";
39290
+ const timeoutMs = options.timeoutMs ?? 30000;
39284
39291
  const headers = {
39285
39292
  Accept: "application/json",
39286
39293
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
@@ -39288,10 +39295,13 @@ async function fetchWithCurlFallback(url, options = {}) {
39288
39295
  if (options.contentType) {
39289
39296
  headers["Content-Type"] = options.contentType;
39290
39297
  }
39298
+ const controller = new AbortController;
39299
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
39291
39300
  try {
39292
39301
  const fetchOptions = {
39293
39302
  method,
39294
- headers
39303
+ headers,
39304
+ signal: controller.signal
39295
39305
  };
39296
39306
  if (options.body) {
39297
39307
  fetchOptions.body = options.body;
@@ -39305,26 +39315,28 @@ async function fetchWithCurlFallback(url, options = {}) {
39305
39315
  }
39306
39316
  } catch (error) {
39307
39317
  logger4.debug(`Native fetch failed, falling back to curl: ${error}`);
39318
+ } finally {
39319
+ clearTimeout(timeout);
39308
39320
  }
39309
39321
  try {
39310
- const curlArgs = ["curl", "-s"];
39322
+ const curlArgs = ["-sS", "--max-time", String(Math.ceil(timeoutMs / 1000))];
39311
39323
  if (method === "POST") {
39312
39324
  curlArgs.push("-X", "POST");
39313
39325
  }
39314
- curlArgs.push("-H", '"Accept: application/json"');
39315
- curlArgs.push("-H", '"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"');
39326
+ curlArgs.push("-H", "Accept: application/json");
39327
+ curlArgs.push("-H", "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)");
39316
39328
  if (options.contentType) {
39317
- curlArgs.push("-H", `"Content-Type: ${options.contentType}"`);
39329
+ curlArgs.push("-H", `Content-Type: ${options.contentType}`);
39318
39330
  }
39319
39331
  if (options.body) {
39320
- curlArgs.push("-d", `'${options.body}'`);
39332
+ curlArgs.push("-d", options.body);
39321
39333
  }
39322
- curlArgs.push(`"${url}"`);
39334
+ curlArgs.push(url);
39323
39335
  if (options.binary) {
39324
- const result2 = execSync(curlArgs.join(" "), { maxBuffer: 50 * 1024 * 1024 });
39336
+ const result2 = execFileSync("curl", curlArgs, { maxBuffer: 50 * 1024 * 1024 });
39325
39337
  return result2;
39326
39338
  }
39327
- const result = execSync(curlArgs.join(" "), {
39339
+ const result = execFileSync("curl", curlArgs, {
39328
39340
  encoding: "utf-8",
39329
39341
  maxBuffer: 50 * 1024 * 1024
39330
39342
  });
@@ -39893,22 +39905,24 @@ var ENDPOINT_3D_MODEL_OBJ = "https://modules.easyeda.com/3dmodel/{uuid}";
39893
39905
  class EasyEDAClient {
39894
39906
  userAgent = "ai-eda-lcsc-mcp/1.0.0";
39895
39907
  async getComponentData(lcscPartNumber) {
39896
- const url = API_ENDPOINT.replace("{lcsc_id}", lcscPartNumber);
39897
- logger5.debug(`Fetching component data for: ${lcscPartNumber}`);
39908
+ const validatedLcscPartNumber = LCSCPartNumberSchema.parse(lcscPartNumber);
39909
+ const url = API_ENDPOINT.replace("{lcsc_id}", validatedLcscPartNumber);
39910
+ logger5.debug(`Fetching component data for: ${validatedLcscPartNumber}`);
39898
39911
  try {
39899
39912
  const responseText = await fetchWithCurlFallback(url);
39900
39913
  const data = JSON.parse(responseText);
39901
39914
  if (!data.result) {
39902
39915
  return null;
39903
39916
  }
39904
- return this.parseComponentData(data.result, lcscPartNumber);
39917
+ return this.parseComponentData(data.result, validatedLcscPartNumber);
39905
39918
  } catch (error) {
39906
39919
  logger5.error(`Failed to fetch component ${lcscPartNumber}:`, error);
39907
39920
  throw error;
39908
39921
  }
39909
39922
  }
39910
39923
  async get3DModel(uuid, format = "step") {
39911
- const url = format === "step" ? ENDPOINT_3D_MODEL_STEP.replace("{uuid}", uuid) : ENDPOINT_3D_MODEL_OBJ.replace("{uuid}", uuid);
39924
+ const validatedUuid = EasyEDAUuidSchema.parse(uuid);
39925
+ const url = format === "step" ? ENDPOINT_3D_MODEL_STEP.replace("{uuid}", validatedUuid) : ENDPOINT_3D_MODEL_OBJ.replace("{uuid}", validatedUuid);
39912
39926
  try {
39913
39927
  const result = await fetchWithCurlFallback(url, { binary: true });
39914
39928
  return result;
@@ -40028,8 +40042,9 @@ class EasyEDACommunityClient {
40028
40042
  }
40029
40043
  }
40030
40044
  async getComponent(uuid) {
40031
- const url = `${API_COMPONENT_ENDPOINT}/${uuid}?version=${API_VERSION}&uuid=${uuid}`;
40032
- logger6.debug(`Fetching component: ${uuid}`);
40045
+ const validatedUuid = EasyEDAUuidSchema.parse(uuid);
40046
+ const url = `${API_COMPONENT_ENDPOINT}/${validatedUuid}?version=${API_VERSION}&uuid=${validatedUuid}`;
40047
+ logger6.debug(`Fetching component: ${validatedUuid}`);
40033
40048
  try {
40034
40049
  const responseText = await fetchWithCurlFallback(url);
40035
40050
  const data = JSON.parse(responseText);
@@ -40038,12 +40053,13 @@ class EasyEDACommunityClient {
40038
40053
  }
40039
40054
  return this.parseComponent(data.result);
40040
40055
  } catch (error) {
40041
- logger6.error(`Failed to fetch component ${uuid}:`, error);
40056
+ logger6.error(`Failed to fetch component ${validatedUuid}:`, error);
40042
40057
  throw error;
40043
40058
  }
40044
40059
  }
40045
40060
  async get3DModel(uuid, format = "step") {
40046
- const url = format === "step" ? ENDPOINT_3D_MODEL_STEP2.replace("{uuid}", uuid) : ENDPOINT_3D_MODEL_OBJ2.replace("{uuid}", uuid);
40061
+ const validatedUuid = EasyEDAUuidSchema.parse(uuid);
40062
+ const url = format === "step" ? ENDPOINT_3D_MODEL_STEP2.replace("{uuid}", validatedUuid) : ENDPOINT_3D_MODEL_OBJ2.replace("{uuid}", validatedUuid);
40047
40063
  try {
40048
40064
  const result = await fetchWithCurlFallback(url, { binary: true });
40049
40065
  return result;
@@ -40178,13 +40194,37 @@ function createComponentService() {
40178
40194
  return {
40179
40195
  async search(query, options = {}) {
40180
40196
  const { source = "lcsc", limit = 20, inStock, basicOnly } = options;
40197
+ if (source === "all") {
40198
+ const [lcscResults, communityResults] = await Promise.all([
40199
+ jlcClient.search(query, { limit, inStock, basicOnly }),
40200
+ easyedaCommunityClient.search({
40201
+ query,
40202
+ limit
40203
+ })
40204
+ ]);
40205
+ return [
40206
+ ...lcscResults,
40207
+ ...communityResults.map((r) => ({
40208
+ id: r.uuid,
40209
+ idType: "easyeda_uuid",
40210
+ easyedaUuid: r.uuid,
40211
+ name: r.title,
40212
+ manufacturer: r.owner?.nickname || "Community",
40213
+ description: r.description || "",
40214
+ package: r.package || "",
40215
+ stock: 0
40216
+ }))
40217
+ ].slice(0, limit);
40218
+ }
40181
40219
  if (source === "community" || source === "easyeda-community") {
40182
40220
  const results = await easyedaCommunityClient.search({
40183
40221
  query,
40184
40222
  limit
40185
40223
  });
40186
40224
  return results.map((r) => ({
40187
- lcscId: r.uuid,
40225
+ id: r.uuid,
40226
+ idType: "easyeda_uuid",
40227
+ easyedaUuid: r.uuid,
40188
40228
  name: r.title,
40189
40229
  manufacturer: r.owner?.nickname || "Community",
40190
40230
  description: r.description || "",
@@ -42890,6 +42930,40 @@ function adaptCommunityComponent(component) {
42890
42930
  rawData: component.rawData
42891
42931
  };
42892
42932
  }
42933
+ function buildValidationData(component, footprintRef, footprintType, isKicadStandardFootprint) {
42934
+ return {
42935
+ component: {
42936
+ name: component.info.name,
42937
+ description: component.info.description,
42938
+ package: component.info.package,
42939
+ manufacturer: component.info.manufacturer,
42940
+ datasheet_url: component.info.datasheetPdf || component.info.datasheet
42941
+ },
42942
+ symbol: {
42943
+ pin_count: component.symbol.pins.length,
42944
+ pins: component.symbol.pins.map((p) => ({
42945
+ number: p.number,
42946
+ name: p.name,
42947
+ electrical_type: p.electricalType
42948
+ }))
42949
+ },
42950
+ footprint: {
42951
+ type: component.footprint.type,
42952
+ pad_count: component.footprint.pads.length,
42953
+ pads: footprintType === "generated" ? component.footprint.pads.map((p) => ({
42954
+ number: p.number,
42955
+ shape: p.shape
42956
+ })) : null,
42957
+ is_kicad_standard: isKicadStandardFootprint,
42958
+ kicad_ref: footprintRef
42959
+ },
42960
+ checks: {
42961
+ pin_pad_count_match: component.symbol.pins.length === component.footprint.pads.length,
42962
+ has_power_pins: component.symbol.pins.some((p) => p.electricalType === "power_in" || p.electricalType === "power_out"),
42963
+ has_ground_pins: component.symbol.pins.some((p) => p.name.toLowerCase().includes("gnd") || p.name.toLowerCase().includes("vss"))
42964
+ }
42965
+ };
42966
+ }
42893
42967
  function createLibraryService() {
42894
42968
  return {
42895
42969
  async install(id, options = {}) {
@@ -42959,6 +43033,34 @@ function createLibraryService() {
42959
43033
  await ensureDir(footprintDir);
42960
43034
  symbolFile = join5(symbolsDir, symLibFile);
42961
43035
  symbolName = component.info.name.replace(/[^a-zA-Z0-9_-]/g, "_");
43036
+ footprintPath = join5(footprintDir, `${symbolName}.kicad_mod`);
43037
+ footprintRef = `${libName}:${symbolName}`;
43038
+ symbolRef = `${libName}:${symbolName}`;
43039
+ component.info.package = footprintRef;
43040
+ if (!options.force && existsSync4(symbolFile)) {
43041
+ const existingContent = await readFile4(symbolFile, "utf-8");
43042
+ if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
43043
+ return {
43044
+ success: true,
43045
+ id,
43046
+ source: "easyeda_community",
43047
+ storageMode: isGlobal ? "global" : "project-local",
43048
+ category,
43049
+ symbolName,
43050
+ symbolRef,
43051
+ footprintRef,
43052
+ footprintType: "generated",
43053
+ datasheet: component.info.datasheet,
43054
+ files: {
43055
+ symbolLibrary: symbolFile,
43056
+ footprint: footprintPath,
43057
+ model3d: undefined
43058
+ },
43059
+ symbolAction: "exists",
43060
+ validationData: buildValidationData(component, footprintRef, "generated", false)
43061
+ };
43062
+ }
43063
+ }
42962
43064
  const include3d = options.include3d ?? true;
42963
43065
  let modelRelativePath;
42964
43066
  if (include3d && component.model3d) {
@@ -42980,10 +43082,7 @@ function createLibraryService() {
42980
43082
  include3DModel: !!modelRelativePath,
42981
43083
  modelPath: modelRelativePath
42982
43084
  });
42983
- footprintPath = join5(footprintDir, `${symbolName}.kicad_mod`);
42984
- footprintRef = `${libName}:${symbolName}`;
42985
43085
  await writeText(footprintPath, footprint);
42986
- component.info.package = footprintRef;
42987
43086
  if (isGlobal) {
42988
43087
  const { ensureGlobalEasyEDALibrary: ensureGlobalEasyEDALibrary2 } = await Promise.resolve().then(() => (init_global_lib_table(), exports_global_lib_table));
42989
43088
  await ensureGlobalEasyEDALibrary2();
@@ -42991,7 +43090,6 @@ function createLibraryService() {
42991
43090
  await ensureSymLibTable(options.projectPath, symbolFile, libName, libDesc);
42992
43091
  await ensureFpLibTable(options.projectPath, footprintDir, libName, libDesc);
42993
43092
  }
42994
- symbolRef = `${libName}:${symbolName}`;
42995
43093
  } else {
42996
43094
  category = getLibraryCategory(component.info.prefix, component.info.category, component.info.description);
42997
43095
  const symbolLibraryFilename = getLibraryFilename(category);
@@ -43001,6 +43099,41 @@ function createLibraryService() {
43001
43099
  await ensureDir(paths.symbolsDir);
43002
43100
  await ensureDir(paths.footprintDir);
43003
43101
  symbolName = symbolConverter.getSymbolName(component);
43102
+ symbolRef = getSymbolReference(category, symbolName);
43103
+ if (!options.force && existsSync4(symbolFile)) {
43104
+ const existingContent = await readFile4(symbolFile, "utf-8");
43105
+ if (symbolConverter.symbolExistsInLibrary(existingContent, component.info.name)) {
43106
+ const existingFootprintResult = footprintConverter.getFootprint(component);
43107
+ if (existingFootprintResult.type === "reference") {
43108
+ footprintRef = existingFootprintResult.reference;
43109
+ footprintPath = undefined;
43110
+ } else {
43111
+ footprintPath = join5(footprintDir, `${existingFootprintResult.name}.kicad_mod`);
43112
+ footprintRef = getFootprintReference(existingFootprintResult.name);
43113
+ }
43114
+ component.info.package = footprintRef;
43115
+ const footprintType2 = existingFootprintResult.type === "reference" ? "reference" : "generated";
43116
+ return {
43117
+ success: true,
43118
+ id,
43119
+ source: "lcsc",
43120
+ storageMode: isGlobal ? "global" : "project-local",
43121
+ category,
43122
+ symbolName,
43123
+ symbolRef,
43124
+ footprintRef,
43125
+ footprintType: footprintType2,
43126
+ datasheet: component.info.datasheet || `https://www.lcsc.com/datasheet/lcsc_datasheet_${id}.pdf`,
43127
+ files: {
43128
+ symbolLibrary: symbolFile,
43129
+ footprint: footprintPath,
43130
+ model3d: undefined
43131
+ },
43132
+ symbolAction: "exists",
43133
+ validationData: buildValidationData(component, footprintRef, footprintType2, existingFootprintResult.type === "reference")
43134
+ };
43135
+ }
43136
+ }
43004
43137
  const include3d = options.include3d ?? true;
43005
43138
  let modelRelativePath;
43006
43139
  if (include3d && component.model3d) {
@@ -43025,7 +43158,6 @@ function createLibraryService() {
43025
43158
  await writeText(footprintPath, footprintResult.content);
43026
43159
  }
43027
43160
  component.info.package = footprintRef;
43028
- symbolRef = getSymbolReference(category, symbolName);
43029
43161
  if (!isGlobal && options.projectPath) {
43030
43162
  await ensureSymLibTable(options.projectPath, symbolFile);
43031
43163
  await ensureFpLibTable(options.projectPath, footprintDir);
@@ -43065,38 +43197,7 @@ function createLibraryService() {
43065
43197
  }
43066
43198
  const isKicadStandardFootprint = !isCommunityComponent && !footprintPath;
43067
43199
  const footprintType = isKicadStandardFootprint ? "reference" : "generated";
43068
- const validationData = {
43069
- component: {
43070
- name: component.info.name,
43071
- description: component.info.description,
43072
- package: component.info.package,
43073
- manufacturer: component.info.manufacturer,
43074
- datasheet_url: component.info.datasheetPdf || component.info.datasheet
43075
- },
43076
- symbol: {
43077
- pin_count: component.symbol.pins.length,
43078
- pins: component.symbol.pins.map((p) => ({
43079
- number: p.number,
43080
- name: p.name,
43081
- electrical_type: p.electricalType
43082
- }))
43083
- },
43084
- footprint: {
43085
- type: component.footprint.type,
43086
- pad_count: component.footprint.pads.length,
43087
- pads: footprintType === "generated" ? component.footprint.pads.map((p) => ({
43088
- number: p.number,
43089
- shape: p.shape
43090
- })) : null,
43091
- is_kicad_standard: isKicadStandardFootprint,
43092
- kicad_ref: footprintRef
43093
- },
43094
- checks: {
43095
- pin_pad_count_match: component.symbol.pins.length === component.footprint.pads.length,
43096
- has_power_pins: component.symbol.pins.some((p) => p.electricalType === "power_in" || p.electricalType === "power_out"),
43097
- has_ground_pins: component.symbol.pins.some((p) => p.name.toLowerCase().includes("gnd") || p.name.toLowerCase().includes("vss"))
43098
- }
43099
- };
43200
+ const validationData = buildValidationData(component, footprintRef, footprintType, isKicadStandardFootprint);
43100
43201
  return {
43101
43202
  success: true,
43102
43203
  id,
@@ -52526,7 +52627,7 @@ function ListView({
52526
52627
  ]
52527
52628
  }, undefined, true, undefined, this)
52528
52629
  ]
52529
- }, r.lcscId, true, undefined, this);
52630
+ }, r.id ?? r.lcscId, true, undefined, this);
52530
52631
  }),
52531
52632
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
52532
52633
  marginTop: 1,
@@ -52573,10 +52674,17 @@ function SearchScreen() {
52573
52674
  } else if (key.downArrow) {
52574
52675
  setSelectedIndex(Math.min(results.length - 1, selectedIndex + 1));
52575
52676
  } else if (key.return && results[selectedIndex]) {
52576
- push("info", {
52577
- componentId: results[selectedIndex].lcscId,
52578
- component: results[selectedIndex]
52579
- });
52677
+ const selected = results[selectedIndex];
52678
+ if (selected.idType === "easyeda_uuid") {
52679
+ push("easyeda-info", {
52680
+ uuid: selected.easyedaUuid ?? selected.id
52681
+ });
52682
+ } else {
52683
+ push("info", {
52684
+ componentId: selected.lcscId ?? selected.id,
52685
+ component: selected
52686
+ });
52687
+ }
52580
52688
  } else if (key.tab) {
52581
52689
  setIsSearching(true);
52582
52690
  const newFiltered = !isFiltered;
@@ -52863,7 +52971,7 @@ async function defaultBrowserId() {
52863
52971
  // ../../node_modules/run-applescript/index.js
52864
52972
  import process16 from "node:process";
52865
52973
  import { promisify as promisify4 } from "node:util";
52866
- import { execFile as execFile4, execFileSync } from "node:child_process";
52974
+ import { execFile as execFile4, execFileSync as execFileSync2 } from "node:child_process";
52867
52975
  var execFileAsync2 = promisify4(execFile4);
52868
52976
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
52869
52977
  if (process16.platform !== "darwin") {
@@ -53572,6 +53680,7 @@ function InfoScreen() {
53572
53680
  fetchData();
53573
53681
  }, [params.componentId, params.component]);
53574
53682
  const datasheetUrl = component && ("datasheetPdf" in component ? component.datasheetPdf : ("datasheet" in component) ? component.datasheet : undefined);
53683
+ const componentLcscId = component?.lcscId ?? params.componentId;
53575
53684
  const [isRegenerating, setIsRegenerating] = import_react30.useState(false);
53576
53685
  const [isDeleting, setIsDeleting] = import_react30.useState(false);
53577
53686
  const [regenerateMessage, setRegenerateMessage] = import_react30.useState(null);
@@ -53579,10 +53688,10 @@ function InfoScreen() {
53579
53688
  if (isLoading || !component || isCheckingLibrary || isRegenerating || isDeleting)
53580
53689
  return;
53581
53690
  const lowerInput = input.toLowerCase();
53582
- if (lowerInput === "r" && installedInfo) {
53691
+ if (lowerInput === "r" && installedInfo && componentLcscId) {
53583
53692
  setIsRegenerating(true);
53584
53693
  setRegenerateMessage("Regenerating symbol and footprint...");
53585
- libraryService.install(component.lcscId, { force: true }).then((result2) => {
53694
+ libraryService.install(componentLcscId, { force: true }).then((result2) => {
53586
53695
  setRegenerateMessage(`✓ Regenerated: ${result2.symbolAction}`);
53587
53696
  setTimeout(() => setRegenerateMessage(null), 2000);
53588
53697
  }).catch((err) => {
@@ -53609,20 +53718,25 @@ function InfoScreen() {
53609
53718
  return;
53610
53719
  }
53611
53720
  if (key.return && !installedInfo) {
53721
+ if (!componentLcscId) {
53722
+ setRegenerateMessage("✗ Missing LCSC part number");
53723
+ setTimeout(() => setRegenerateMessage(null), 3000);
53724
+ return;
53725
+ }
53612
53726
  if (checkingRef.current)
53613
53727
  return;
53614
53728
  checkingRef.current = true;
53615
53729
  setIsCheckingLibrary(true);
53616
53730
  if (libraryStatus && (!libraryStatus.installed || !libraryStatus.linked)) {
53617
53731
  push("library-setup", {
53618
- componentId: component.lcscId,
53732
+ componentId: componentLcscId,
53619
53733
  component
53620
53734
  });
53621
53735
  setIsCheckingLibrary(false);
53622
53736
  checkingRef.current = false;
53623
53737
  } else {
53624
53738
  push("install", {
53625
- componentId: component.lcscId,
53739
+ componentId: componentLcscId,
53626
53740
  component
53627
53741
  });
53628
53742
  setIsCheckingLibrary(false);
@@ -53670,6 +53784,21 @@ function InfoScreen() {
53670
53784
  ]
53671
53785
  }, undefined, true, undefined, this);
53672
53786
  }
53787
+ if (!componentLcscId) {
53788
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
53789
+ flexDirection: "column",
53790
+ children: [
53791
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
53792
+ color: "red",
53793
+ children: "✗ Missing LCSC part number"
53794
+ }, undefined, false, undefined, this),
53795
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
53796
+ dimColor: true,
53797
+ children: "Press Esc to go back"
53798
+ }, undefined, false, undefined, this)
53799
+ ]
53800
+ }, undefined, true, undefined, this);
53801
+ }
53673
53802
  return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
53674
53803
  flexDirection: "column",
53675
53804
  children: [
@@ -53681,7 +53810,7 @@ function InfoScreen() {
53681
53810
  "Component: ",
53682
53811
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
53683
53812
  color: "cyan",
53684
- children: component.lcscId
53813
+ children: componentLcscId
53685
53814
  }, undefined, false, undefined, this),
53686
53815
  " ",
53687
53816
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
@@ -53700,7 +53829,7 @@ function InfoScreen() {
53700
53829
  }, undefined, true, undefined, this)
53701
53830
  }, undefined, false, undefined, this),
53702
53831
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(DetailView, {
53703
- component,
53832
+ component: { ...component, lcscId: componentLcscId },
53704
53833
  terminalWidth,
53705
53834
  isInstalled: !!installedInfo,
53706
53835
  installedInfo,
@@ -53923,7 +54052,7 @@ function InstalledScreen() {
53923
54052
  }, undefined, true, undefined, this)
53924
54053
  }, undefined, false, undefined, this),
53925
54054
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(InstalledView, {
53926
- component: params.component,
54055
+ component: { ...params.component, lcscId: params.componentId },
53927
54056
  result: result2,
53928
54057
  error: params.error || null,
53929
54058
  terminalWidth
@@ -54972,10 +55101,53 @@ async function renderApp(initialScreen, initialParams) {
54972
55101
  await instance.waitUntilExit();
54973
55102
  }
54974
55103
 
55104
+ // src/utils/agent-output.ts
55105
+ function printJson(value) {
55106
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
55107
+ `);
55108
+ }
55109
+ function printJsonError(code, message, options = {}) {
55110
+ const payload = {
55111
+ success: false,
55112
+ error: {
55113
+ code,
55114
+ message,
55115
+ retryable: options.retryable ?? false,
55116
+ details: options.details
55117
+ }
55118
+ };
55119
+ printJson(payload);
55120
+ }
55121
+ function getErrorMessage(error) {
55122
+ return error instanceof Error ? error.message : String(error);
55123
+ }
55124
+
55125
+ // src/utils/search-result-output.ts
55126
+ function formatSearchResultForJson(result2) {
55127
+ return {
55128
+ id: result2.id,
55129
+ id_type: result2.idType,
55130
+ lcsc_id: result2.idType === "lcsc" ? result2.lcscId : undefined,
55131
+ easyeda_uuid: result2.idType === "easyeda_uuid" ? result2.easyedaUuid ?? result2.id : undefined,
55132
+ name: result2.name,
55133
+ manufacturer: result2.manufacturer,
55134
+ description: result2.description,
55135
+ package: result2.package,
55136
+ datasheet: result2.datasheetPdf,
55137
+ stock: result2.stock,
55138
+ price: result2.price,
55139
+ library_type: result2.libraryType,
55140
+ category: result2.category,
55141
+ attributes: result2.attributes
55142
+ };
55143
+ }
55144
+
54975
55145
  // src/commands/search.ts
54976
55146
  var componentService5 = createComponentService();
54977
55147
  async function searchCommand(query, options) {
54978
- console.log(`Searching for "${query}"...`);
55148
+ if (!options.json) {
55149
+ console.log(`Searching for "${query}"...`);
55150
+ }
54979
55151
  try {
54980
55152
  let results = await componentService5.search(query, options);
54981
55153
  if (!options.basicOnly) {
@@ -54988,17 +55160,58 @@ async function searchCommand(query, options) {
54988
55160
  });
54989
55161
  }
54990
55162
  if (results.length === 0) {
55163
+ if (options.json) {
55164
+ printJson({
55165
+ success: true,
55166
+ query,
55167
+ count: 0,
55168
+ results: []
55169
+ });
55170
+ return;
55171
+ }
54991
55172
  console.log("No components found. Try a different search term.");
54992
55173
  return;
54993
55174
  }
55175
+ if (options.json) {
55176
+ printJson({
55177
+ success: true,
55178
+ query,
55179
+ count: results.length,
55180
+ results: results.map(formatSearchResultForJson)
55181
+ });
55182
+ return;
55183
+ }
54994
55184
  process.stdout.write("\x1B[1A\x1B[2K");
54995
55185
  await renderApp("search", { query, results });
54996
55186
  } catch (error) {
54997
- console.error(`Search failed: ${error instanceof Error ? error.message : "Unknown error"}`);
55187
+ if (options.json) {
55188
+ printJsonError("search_failed", getErrorMessage(error), { retryable: true });
55189
+ } else {
55190
+ console.error(`Search failed: ${getErrorMessage(error)}`);
55191
+ }
54998
55192
  process.exit(1);
54999
55193
  }
55000
55194
  }
55001
55195
 
55196
+ // src/commands/info.ts
55197
+ var componentService6 = createComponentService();
55198
+ async function infoCommand(id, options) {
55199
+ if (options.json) {
55200
+ try {
55201
+ const details = await componentService6.getDetails(id);
55202
+ printJson({
55203
+ success: true,
55204
+ component: details
55205
+ });
55206
+ } catch (error) {
55207
+ printJsonError("component_fetch_failed", getErrorMessage(error), { retryable: true });
55208
+ process.exit(1);
55209
+ }
55210
+ return;
55211
+ }
55212
+ await renderApp("info", { componentId: id });
55213
+ }
55214
+
55002
55215
  // ../../node_modules/@clack/core/dist/index.mjs
55003
55216
  var import_sisteransi = __toESM(require_src(), 1);
55004
55217
  var import_picocolors = __toESM(require_picocolors(), 1);
@@ -55576,26 +55789,6 @@ var L2 = () => {
55576
55789
  } };
55577
55790
  };
55578
55791
 
55579
- // src/commands/info.ts
55580
- var componentService6 = createComponentService();
55581
- async function infoCommand(id, options) {
55582
- if (options.json) {
55583
- const spinner = L2();
55584
- spinner.start(`Fetching component ${id}...`);
55585
- try {
55586
- const details = await componentService6.getDetails(id);
55587
- spinner.stop("Component found");
55588
- console.log(JSON.stringify(details, null, 2));
55589
- } catch (error) {
55590
- spinner.stop("Failed to fetch component");
55591
- v2.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
55592
- process.exit(1);
55593
- }
55594
- return;
55595
- }
55596
- await renderApp("info", { componentId: id });
55597
- }
55598
-
55599
55792
  // src/commands/install.ts
55600
55793
  var componentService7 = createComponentService();
55601
55794
  var libraryService6 = createLibraryService();
@@ -55603,22 +55796,43 @@ function isLcscId(id) {
55603
55796
  return /^C\d+$/i.test(id);
55604
55797
  }
55605
55798
  async function installCommand(id, options) {
55799
+ if (!id && (options.yes || options.json)) {
55800
+ if (options.json) {
55801
+ printJsonError("missing_id", "Direct install requires an LCSC part number");
55802
+ } else {
55803
+ v2.error("Direct install requires an LCSC part number.");
55804
+ }
55805
+ process.exit(1);
55806
+ }
55606
55807
  if (id && !isLcscId(id)) {
55808
+ if (options.json) {
55809
+ printJsonError("invalid_lcsc_id", `"${id}" is not an LCSC part number`, {
55810
+ details: { hint: `Use jlc easyeda install ${id}` }
55811
+ });
55812
+ process.exit(1);
55813
+ }
55607
55814
  v2.error(`"${id}" is not an LCSC part number (e.g., C2040).`);
55608
55815
  v2.info(`For EasyEDA community components, use: ${source_default.cyan(`jlc easyeda install ${id}`)}`);
55609
55816
  process.exit(1);
55610
55817
  }
55611
- if (id && options.force) {
55612
- const spinner = L2();
55613
- spinner.start(`Installing component ${id}...`);
55818
+ if (id && (options.yes || options.json)) {
55819
+ const spinner = options.json ? null : L2();
55820
+ spinner?.start(`Installing component ${id}...`);
55614
55821
  try {
55615
55822
  await libraryService6.ensureGlobalTables();
55616
55823
  const result2 = await libraryService6.install(id, {
55617
55824
  projectPath: options.projectPath,
55618
55825
  include3d: options.include3d,
55619
- force: true
55826
+ force: options.force
55620
55827
  });
55621
- spinner.stop(source_default.green("✓ Component installed"));
55828
+ spinner?.stop(source_default.green("✓ Component installed"));
55829
+ if (options.json) {
55830
+ printJson({
55831
+ success: true,
55832
+ result: result2
55833
+ });
55834
+ return;
55835
+ }
55622
55836
  console.log();
55623
55837
  console.log(source_default.cyan("Symbol: "), result2.symbolRef);
55624
55838
  console.log(source_default.cyan("Footprint: "), result2.footprintRef);
@@ -55629,8 +55843,12 @@ async function installCommand(id, options) {
55629
55843
  console.log();
55630
55844
  console.log(source_default.dim(`Library: ${result2.files.symbolLibrary}`));
55631
55845
  } catch (error) {
55632
- spinner.stop(source_default.red("✗ Installation failed"));
55633
- v2.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
55846
+ spinner?.stop(source_default.red("✗ Installation failed"));
55847
+ if (options.json) {
55848
+ printJsonError("install_failed", getErrorMessage(error), { retryable: true });
55849
+ } else {
55850
+ v2.error(`Error: ${getErrorMessage(error)}`);
55851
+ }
55634
55852
  process.exit(1);
55635
55853
  }
55636
55854
  return;
@@ -55682,15 +55900,13 @@ async function installCommand(id, options) {
55682
55900
  var libraryService7 = createLibraryService();
55683
55901
  async function libraryCommand(options) {
55684
55902
  if (options.json) {
55685
- const spinner = L2();
55686
- spinner.start("Loading library status...");
55687
55903
  try {
55688
55904
  const [status, components] = await Promise.all([
55689
55905
  libraryService7.getStatus(),
55690
55906
  libraryService7.listInstalled({})
55691
55907
  ]);
55692
- spinner.stop(`Found ${components.length} installed components`);
55693
- console.log(JSON.stringify({
55908
+ printJson({
55909
+ success: true,
55694
55910
  status: {
55695
55911
  installed: status.installed,
55696
55912
  linked: status.linked,
@@ -55699,10 +55915,9 @@ async function libraryCommand(options) {
55699
55915
  paths: status.paths
55700
55916
  },
55701
55917
  components
55702
- }, null, 2));
55918
+ });
55703
55919
  } catch (error) {
55704
- spinner.stop("Failed to get library status");
55705
- v2.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
55920
+ printJsonError("library_status_failed", getErrorMessage(error), { retryable: false });
55706
55921
  process.exit(1);
55707
55922
  }
55708
55923
  return;
@@ -55748,6 +55963,24 @@ async function regenerateCommand(options) {
55748
55963
  var componentService8 = createComponentService();
55749
55964
  var libraryService8 = createLibraryService();
55750
55965
  async function easyedaSearchCommand(query, options) {
55966
+ if (options.json) {
55967
+ try {
55968
+ const results = await componentService8.search(query, {
55969
+ limit: 20,
55970
+ source: "easyeda-community"
55971
+ });
55972
+ printJson({
55973
+ success: true,
55974
+ query,
55975
+ count: results.length,
55976
+ results: results.map(formatSearchResultForJson)
55977
+ });
55978
+ } catch (error) {
55979
+ printJsonError("easyeda_search_failed", getErrorMessage(error), { retryable: true });
55980
+ process.exit(1);
55981
+ }
55982
+ return;
55983
+ }
55751
55984
  const port = options.port ?? 3847;
55752
55985
  console.log("Starting component browser...");
55753
55986
  startHttpServer({
@@ -55755,7 +55988,13 @@ async function easyedaSearchCommand(query, options) {
55755
55988
  onReady: async (url) => {
55756
55989
  const searchUrl = query ? `${url}?q=${encodeURIComponent(query)}` : url;
55757
55990
  console.log(`Browser opened at ${searchUrl}`);
55758
- await open_default(searchUrl);
55991
+ if (options.open !== false) {
55992
+ await open_default(searchUrl);
55993
+ }
55994
+ if (options.once) {
55995
+ stopHttpServer();
55996
+ process.exit(0);
55997
+ }
55759
55998
  console.log("Press Ctrl+C to stop the server and exit");
55760
55999
  }
55761
56000
  });
@@ -55770,17 +56009,32 @@ Shutting down server...`);
55770
56009
  });
55771
56010
  }
55772
56011
  async function easyedaInstallCommand(uuid, options) {
55773
- if (uuid && options.force) {
55774
- const spinner = L2();
55775
- spinner.start(`Installing EasyEDA component ${uuid}...`);
56012
+ if (!uuid && (options.yes || options.json)) {
56013
+ if (options.json) {
56014
+ printJsonError("missing_uuid", "Direct EasyEDA install requires a component UUID");
56015
+ } else {
56016
+ v2.error("Direct EasyEDA install requires a component UUID.");
56017
+ }
56018
+ process.exit(1);
56019
+ }
56020
+ if (uuid && (options.yes || options.json)) {
56021
+ const spinner = options.json ? null : L2();
56022
+ spinner?.start(`Installing EasyEDA component ${uuid}...`);
55776
56023
  try {
55777
56024
  await libraryService8.ensureGlobalTables();
55778
56025
  const result2 = await libraryService8.install(uuid, {
55779
56026
  projectPath: options.projectPath,
55780
56027
  include3d: options.include3d,
55781
- force: true
56028
+ force: options.force
55782
56029
  });
55783
- spinner.stop(source_default.green("✓ Component installed"));
56030
+ spinner?.stop(source_default.green("✓ Component installed"));
56031
+ if (options.json) {
56032
+ printJson({
56033
+ success: true,
56034
+ result: result2
56035
+ });
56036
+ return;
56037
+ }
55784
56038
  console.log();
55785
56039
  console.log(source_default.cyan("Symbol: "), result2.symbolRef);
55786
56040
  console.log(source_default.cyan("Footprint: "), result2.footprintRef);
@@ -55791,8 +56045,12 @@ async function easyedaInstallCommand(uuid, options) {
55791
56045
  console.log();
55792
56046
  console.log(source_default.dim(`Library: ${result2.files.symbolLibrary}`));
55793
56047
  } catch (error) {
55794
- spinner.stop(source_default.red("✗ Installation failed"));
55795
- v2.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
56048
+ spinner?.stop(source_default.red("✗ Installation failed"));
56049
+ if (options.json) {
56050
+ printJsonError("easyeda_install_failed", getErrorMessage(error), { retryable: true });
56051
+ } else {
56052
+ v2.error(`Error: ${getErrorMessage(error)}`);
56053
+ }
55796
56054
  process.exit(1);
55797
56055
  }
55798
56056
  return;
@@ -55894,33 +56152,25 @@ async function validateComponent(lcscCode, options) {
55894
56152
  }
55895
56153
  }
55896
56154
  async function validateCommand(id, options) {
56155
+ if (options.footprintOnly && options.symbolOnly) {
56156
+ if (options.json) {
56157
+ printJsonError("invalid_options", "--footprint-only and --symbol-only are mutually exclusive");
56158
+ } else {
56159
+ v2.error("--footprint-only and --symbol-only are mutually exclusive");
56160
+ }
56161
+ process.exit(1);
56162
+ }
55897
56163
  const validateFootprint = !options.symbolOnly;
55898
56164
  const validateSymbol = !options.footprintOnly;
55899
56165
  if (id && !options.category && !options.all) {
55900
- const spinner = L2();
55901
- spinner.start(`Validating ${id}...`);
56166
+ const spinner = options.json ? null : L2();
56167
+ spinner?.start(`Validating ${id}...`);
55902
56168
  const result2 = await validateComponent(id, {
55903
56169
  footprint: validateFootprint,
55904
56170
  symbol: validateSymbol
55905
56171
  });
55906
- spinner.stop(result2.passed ? source_default.green("✓ Validation passed") : source_default.red("✗ Validation failed"));
55907
- console.log();
55908
- console.log(source_default.bold(`${result2.componentName} (${result2.lcscCode})`));
55909
- console.log(source_default.dim(`Duration: ${result2.durationMs}ms`));
55910
- console.log();
55911
- if (result2.error) {
55912
- console.log(source_default.red(`Error: ${result2.error}`));
55913
- process.exit(1);
55914
- }
55915
- if (result2.footprint) {
55916
- console.log(formatComparisonResult(result2.footprint));
55917
- }
55918
- if (result2.symbol) {
55919
- console.log(formatSymbolComparisonResult(result2.symbol));
55920
- }
55921
- if (options.json) {
55922
- console.log(JSON.stringify(result2, null, 2));
55923
- }
56172
+ spinner?.stop(result2.passed ? source_default.green("✓ Validation passed") : source_default.red("✗ Validation failed"));
56173
+ let htmlPath2;
55924
56174
  if (options.html) {
55925
56175
  const reference = await fetchReferenceSVG(id);
55926
56176
  const componentData = await easyedaClient.getComponentData(id);
@@ -55942,12 +56192,41 @@ async function validateCommand(id, options) {
55942
56192
  }
55943
56193
  const html = generateValidationReport(result2, svgs);
55944
56194
  writeFileSync(options.html, html);
55945
- console.log(source_default.dim(`HTML report: ${options.html}`));
56195
+ htmlPath2 = options.html;
56196
+ }
56197
+ if (options.json) {
56198
+ printJson({
56199
+ success: result2.passed && !result2.error,
56200
+ result: result2,
56201
+ html: htmlPath2
56202
+ });
56203
+ process.exit(result2.passed && !result2.error ? 0 : 1);
56204
+ }
56205
+ console.log();
56206
+ console.log(source_default.bold(`${result2.componentName} (${result2.lcscCode})`));
56207
+ console.log(source_default.dim(`Duration: ${result2.durationMs}ms`));
56208
+ console.log();
56209
+ if (result2.error) {
56210
+ console.log(source_default.red(`Error: ${result2.error}`));
56211
+ process.exit(1);
56212
+ }
56213
+ if (result2.footprint) {
56214
+ console.log(formatComparisonResult(result2.footprint));
56215
+ }
56216
+ if (result2.symbol) {
56217
+ console.log(formatSymbolComparisonResult(result2.symbol));
56218
+ }
56219
+ if (htmlPath2) {
56220
+ console.log(source_default.dim(`HTML report: ${htmlPath2}`));
55946
56221
  }
55947
56222
  process.exit(result2.passed ? 0 : 1);
55948
56223
  }
55949
56224
  let components = options.all ? getAllTestComponents() : options.category ? getTestComponentsByCategory(options.category) : [];
55950
56225
  if (components.length === 0 && !id) {
56226
+ if (options.json) {
56227
+ printJsonError("missing_validation_target", "JSON validation requires an id, --category, or --all");
56228
+ process.exit(1);
56229
+ }
55951
56230
  const categories = getCategoryNames();
55952
56231
  const selected = await de({
55953
56232
  message: "Select a category to validate:",
@@ -55964,18 +56243,30 @@ async function validateCommand(id, options) {
55964
56243
  }
55965
56244
  components = components.filter((c2) => !c2.skip);
55966
56245
  if (components.length === 0) {
56246
+ if (options.json) {
56247
+ printJson({
56248
+ success: true,
56249
+ summary: { total: 0, passed: 0, failed: 0, errors: 0 },
56250
+ results: []
56251
+ });
56252
+ process.exit(0);
56253
+ }
55967
56254
  v2.warn("No components to validate");
55968
56255
  process.exit(0);
55969
56256
  }
55970
- console.log(source_default.bold(`
56257
+ if (!options.json) {
56258
+ console.log(source_default.bold(`
55971
56259
  Validating ${components.length} components...
55972
56260
  `));
56261
+ }
55973
56262
  const results = [];
55974
56263
  let passed = 0;
55975
56264
  let failed = 0;
55976
56265
  let errors = 0;
55977
56266
  for (const component of components) {
55978
- process.stdout.write(` ${component.lcsc.padEnd(12)} ${component.name.slice(0, 40).padEnd(42)} `);
56267
+ if (!options.json) {
56268
+ process.stdout.write(` ${component.lcsc.padEnd(12)} ${component.name.slice(0, 40).padEnd(42)} `);
56269
+ }
55979
56270
  const result2 = await validateComponent(component.lcsc, {
55980
56271
  footprint: validateFootprint,
55981
56272
  symbol: validateSymbol
@@ -55983,49 +56274,79 @@ Validating ${components.length} components...
55983
56274
  results.push(result2);
55984
56275
  if (result2.error) {
55985
56276
  errors++;
55986
- console.log(source_default.yellow("⚠ ERROR"));
56277
+ if (!options.json)
56278
+ console.log(source_default.yellow("⚠ ERROR"));
55987
56279
  } else if (result2.passed) {
55988
56280
  passed++;
55989
- console.log(source_default.green("✓ PASS"));
56281
+ if (!options.json)
56282
+ console.log(source_default.green("✓ PASS"));
55990
56283
  } else {
55991
56284
  failed++;
55992
- console.log(source_default.red("✗ FAIL"));
56285
+ if (!options.json)
56286
+ console.log(source_default.red("✗ FAIL"));
55993
56287
  }
55994
56288
  }
56289
+ let htmlPath;
56290
+ if (options.html) {
56291
+ const html = generateBatchReport(results, "JLC-CLI Validation Report");
56292
+ writeFileSync(options.html, html);
56293
+ htmlPath = options.html;
56294
+ }
56295
+ if (options.json) {
56296
+ printJson({
56297
+ success: failed === 0 && errors === 0,
56298
+ summary: {
56299
+ total: results.length,
56300
+ passed,
56301
+ failed,
56302
+ errors
56303
+ },
56304
+ results,
56305
+ html: htmlPath
56306
+ });
56307
+ process.exit(failed > 0 || errors > 0 ? 1 : 0);
56308
+ }
55995
56309
  console.log();
55996
56310
  console.log(source_default.bold("Summary:"));
55997
56311
  console.log(` ${source_default.green(`${passed} passed`)} ${source_default.red(`${failed} failed`)} ${source_default.yellow(`${errors} errors`)}`);
55998
56312
  console.log();
55999
- if (options.json) {
56000
- console.log(JSON.stringify(results, null, 2));
56001
- }
56002
- if (options.html) {
56003
- const html = generateBatchReport(results, "JLC-CLI Validation Report");
56004
- writeFileSync(options.html, html);
56005
- console.log(source_default.dim(`HTML report: ${options.html}`));
56313
+ if (htmlPath) {
56314
+ console.log(source_default.dim(`HTML report: ${htmlPath}`));
56006
56315
  }
56007
56316
  process.exit(failed > 0 || errors > 0 ? 1 : 0);
56008
56317
  }
56009
56318
 
56010
56319
  // src/index.ts
56320
+ var require2 = createRequire2(import.meta.url);
56321
+ var { version } = require2("../package.json");
56011
56322
  var program2 = new Command;
56012
- program2.name("jlc").description("JLC/EasyEDA component sourcing and KiCad library management").version("0.1.0");
56013
- program2.command("search <query...>").description("Search for components (basic parts sorted first)").option("-l, --limit <number>", "Maximum results", "20").option("--in-stock", "Only show in-stock components").option("--basic-only", "Only show basic parts (no extended)").option("--community", "Search EasyEDA community library").action(async (queryParts, options) => {
56323
+ function parsePositiveInt(value) {
56324
+ const parsed = Number.parseInt(value, 10);
56325
+ if (!Number.isInteger(parsed) || parsed <= 0) {
56326
+ throw new Error(`Expected a positive integer, got "${value}"`);
56327
+ }
56328
+ return parsed;
56329
+ }
56330
+ program2.name("jlc").description("JLC/EasyEDA component sourcing and KiCad library management").version(version);
56331
+ program2.command("search <query...>").description("Search for components (basic parts sorted first)").option("-l, --limit <number>", "Maximum results", parsePositiveInt, 20).option("--in-stock", "Only show in-stock components").option("--basic-only", "Only show basic parts (no extended)").option("--community", "Search EasyEDA community library").option("--json", "Output clean JSON and do not launch the TUI").action(async (queryParts, options) => {
56014
56332
  const query = queryParts.join(" ");
56015
56333
  await searchCommand(query, {
56016
- limit: parseInt(options.limit, 10),
56334
+ limit: options.limit,
56017
56335
  inStock: options.inStock,
56018
56336
  basicOnly: options.basicOnly || false,
56019
- source: options.community ? "easyeda-community" : "lcsc"
56337
+ source: options.community ? "easyeda-community" : "lcsc",
56338
+ json: options.json
56020
56339
  });
56021
56340
  });
56022
56341
  program2.command("info <id>").description("Get component details").option("--json", "Output as JSON").action(async (id, options) => {
56023
56342
  await infoCommand(id, { json: options.json });
56024
56343
  });
56025
- program2.command("install [id]").description("Install component to KiCad libraries").option("-p, --project <path>", "Install to project-local library").option("--with-3d", "Include 3D model").option("-f, --force", "Force reinstall (regenerate symbol and footprint)").action(async (id, options) => {
56344
+ program2.command("install [id]").description("Install component to KiCad libraries").option("-p, --project <path>", "Install to project-local library").option("--with-3d", "Include 3D model").option("-y, --yes", "Install directly without launching the TUI").option("--json", "Output clean JSON and install directly").option("-f, --force", "Force reinstall (regenerate symbol and footprint)").action(async (id, options) => {
56026
56345
  await installCommand(id, {
56027
56346
  projectPath: options.project,
56028
56347
  include3d: options.with3d,
56348
+ yes: options.yes,
56349
+ json: options.json,
56029
56350
  force: options.force
56030
56351
  });
56031
56352
  });
@@ -56052,17 +56373,25 @@ program2.command("validate [id]").description("Validate footprint/symbol generat
56052
56373
  });
56053
56374
  });
56054
56375
  var easyeda = program2.command("easyeda").description("EasyEDA community component browser");
56055
- easyeda.command("search <query...>").description("Open browser-based component search").option("-p, --port <number>", "HTTP server port", "3847").action(async (queryParts, options) => {
56376
+ easyeda.command("search <query...>").description("Open browser-based component search").option("-p, --port <number>", "HTTP server port", parsePositiveInt, 3847).option("--json", "Output clean JSON search results and do not open a browser").option("--no-open", "Start the browser server without opening a browser").option("--once", "Print the browser URL and exit after the server is ready").action(async (queryParts, options) => {
56056
56377
  const query = queryParts.join(" ");
56057
56378
  await easyedaSearchCommand(query, {
56058
- port: options.port ? parseInt(options.port, 10) : undefined
56379
+ port: options.port,
56380
+ json: options.json,
56381
+ open: options.open,
56382
+ once: options.once
56059
56383
  });
56060
56384
  });
56061
- easyeda.command("install [uuid]").description("Install EasyEDA community component to KiCad libraries").option("-p, --project <path>", "Install to project-local library").option("--with-3d", "Include 3D model").option("-f, --force", "Force reinstall (regenerate symbol and footprint)").action(async (uuid, options) => {
56385
+ easyeda.command("install [uuid]").description("Install EasyEDA community component to KiCad libraries").option("-p, --project <path>", "Install to project-local library").option("--with-3d", "Include 3D model").option("-y, --yes", "Install directly without launching the TUI").option("--json", "Output clean JSON and install directly").option("-f, --force", "Force reinstall (regenerate symbol and footprint)").action(async (uuid, options) => {
56062
56386
  await easyedaInstallCommand(uuid, {
56063
56387
  projectPath: options.project,
56064
56388
  include3d: options.with3d,
56389
+ yes: options.yes,
56390
+ json: options.json,
56065
56391
  force: options.force
56066
56392
  });
56067
56393
  });
56068
- program2.parse();
56394
+ program2.parseAsync().catch((error) => {
56395
+ console.error(`Error: ${getErrorMessage(error)}`);
56396
+ process.exit(1);
56397
+ });