@jlcpcb/core 0.1.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +474 -0
  3. package/package.json +48 -0
  4. package/src/api/easyeda-community.ts +259 -0
  5. package/src/api/easyeda.ts +153 -0
  6. package/src/api/index.ts +7 -0
  7. package/src/api/jlc.ts +185 -0
  8. package/src/constants/design-rules.ts +119 -0
  9. package/src/constants/footprints.ts +68 -0
  10. package/src/constants/index.ts +7 -0
  11. package/src/constants/kicad.ts +147 -0
  12. package/src/converter/category-router.ts +638 -0
  13. package/src/converter/footprint-mapper.ts +236 -0
  14. package/src/converter/footprint.ts +949 -0
  15. package/src/converter/global-lib-table.ts +394 -0
  16. package/src/converter/index.ts +46 -0
  17. package/src/converter/lib-table.ts +181 -0
  18. package/src/converter/svg-arc.ts +179 -0
  19. package/src/converter/symbol-templates.ts +214 -0
  20. package/src/converter/symbol.ts +1682 -0
  21. package/src/converter/value-normalizer.ts +262 -0
  22. package/src/index.ts +25 -0
  23. package/src/parsers/easyeda-shapes.ts +628 -0
  24. package/src/parsers/http-client.ts +96 -0
  25. package/src/parsers/index.ts +38 -0
  26. package/src/parsers/utils.ts +29 -0
  27. package/src/services/component-service.ts +100 -0
  28. package/src/services/fix-service.ts +50 -0
  29. package/src/services/index.ts +9 -0
  30. package/src/services/library-service.ts +696 -0
  31. package/src/types/component.ts +61 -0
  32. package/src/types/easyeda-community.ts +78 -0
  33. package/src/types/easyeda.ts +356 -0
  34. package/src/types/index.ts +12 -0
  35. package/src/types/jlc.ts +84 -0
  36. package/src/types/kicad.ts +136 -0
  37. package/src/types/mcp.ts +77 -0
  38. package/src/types/project.ts +60 -0
  39. package/src/utils/conversion.ts +104 -0
  40. package/src/utils/file-system.ts +143 -0
  41. package/src/utils/index.ts +8 -0
  42. package/src/utils/logger.ts +96 -0
  43. package/src/utils/validation.ts +110 -0
  44. package/tsconfig.json +9 -0
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Global Library Table Manager
3
+ * Registers JLC libraries in KiCad's global sym-lib-table and fp-lib-table
4
+ * Works cross-platform: macOS, Windows, Linux
5
+ */
6
+
7
+ import { readFile, writeFile, mkdir } from 'fs/promises';
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir, platform } from 'os';
11
+ import {
12
+ getAllCategories,
13
+ getLibraryFilename,
14
+ getFootprintDirName,
15
+ get3DModelsDirName,
16
+ type LibraryCategory,
17
+ } from './category-router.js';
18
+ import { libraryExistsInTable, addLibraryToTable } from './lib-table.js';
19
+
20
+ // KiCad versions to check (newest first)
21
+ const KICAD_VERSIONS = ['9.0', '8.0'];
22
+
23
+ // Library prefix - matches category-router.ts
24
+ const LIBRARY_PREFIX = 'JLC-MCP';
25
+
26
+ // 3rd party library namespace (subfolder under 3rdparty/)
27
+ const LIBRARY_NAMESPACE = 'jlc_mcp';
28
+
29
+ // KiCad environment variable for 3rd party libraries
30
+ // This resolves to ~/Documents/KiCad/{version}/3rdparty/ on all platforms
31
+ const KICAD_3RD_PARTY_VAR = '${KICAD9_3RD_PARTY}';
32
+
33
+ /**
34
+ * Detect KiCad major version from existing user directories
35
+ */
36
+ function detectKicadVersion(): string {
37
+ const home = homedir();
38
+ const baseDir = join(home, 'Documents', 'KiCad');
39
+
40
+ for (const version of KICAD_VERSIONS) {
41
+ if (existsSync(join(baseDir, version))) {
42
+ return version;
43
+ }
44
+ }
45
+ return '9.0'; // Default
46
+ }
47
+
48
+ /**
49
+ * Get KiCad global config directory (platform-specific)
50
+ * This is where sym-lib-table and fp-lib-table are stored
51
+ */
52
+ function getKicadConfigDir(version: string): string {
53
+ const home = homedir();
54
+ const plat = platform();
55
+
56
+ if (plat === 'darwin') {
57
+ return join(home, 'Library', 'Preferences', 'kicad', version);
58
+ } else if (plat === 'win32') {
59
+ return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'kicad', version);
60
+ } else {
61
+ // Linux and others
62
+ return join(home, '.config', 'kicad', version);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get KiCad 3rd party library base directory (where libraries are stored)
68
+ * Platform-specific paths matching where ${KICAD9_3RD_PARTY} resolves:
69
+ * - macOS/Windows: ~/Documents/KiCad/{version}/3rdparty/
70
+ * - Linux: ~/.local/share/kicad/{version}/3rdparty/
71
+ */
72
+ function get3rdPartyBaseDir(version: string): string {
73
+ const home = homedir();
74
+ const plat = platform();
75
+
76
+ if (plat === 'linux') {
77
+ return join(home, '.local', 'share', 'kicad', version, '3rdparty', LIBRARY_NAMESPACE);
78
+ }
79
+ // macOS and Windows use Documents/KiCad
80
+ return join(home, 'Documents', 'KiCad', version, '3rdparty', LIBRARY_NAMESPACE);
81
+ }
82
+
83
+ /**
84
+ * Get absolute path to a JLC-MCP symbol library file (for file operations)
85
+ */
86
+ function getSymbolLibPath(category: LibraryCategory, version: string): string {
87
+ return join(get3rdPartyBaseDir(version), 'symbols', getLibraryFilename(category));
88
+ }
89
+
90
+ /**
91
+ * Get portable URI for a JLC-MCP symbol library (for table entries)
92
+ * Uses ${KICAD9_3RD_PARTY} variable for cross-system compatibility
93
+ */
94
+ function getSymbolLibUri(category: LibraryCategory): string {
95
+ return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/symbols/${getLibraryFilename(category)}`;
96
+ }
97
+
98
+ /**
99
+ * Get absolute path to JLC-MCP footprint library directory (for file operations)
100
+ */
101
+ function getFootprintLibPath(version: string): string {
102
+ return join(get3rdPartyBaseDir(version), 'footprints', getFootprintDirName());
103
+ }
104
+
105
+ /**
106
+ * Get portable URI for JLC-MCP footprint library (for table entries)
107
+ * Uses ${KICAD9_3RD_PARTY} variable for cross-system compatibility
108
+ */
109
+ function getFootprintLibUri(): string {
110
+ return `${KICAD_3RD_PARTY_VAR}/${LIBRARY_NAMESPACE}/footprints/${getFootprintDirName()}`;
111
+ }
112
+
113
+ /**
114
+ * Generate standard KiCad symbol library header
115
+ */
116
+ function generateEmptySymbolLibrary(): string {
117
+ return `(kicad_symbol_lib
118
+ \t(version 20241209)
119
+ \t(generator "jlc-mcp")
120
+ \t(generator_version "9.0")
121
+ )\n`;
122
+ }
123
+
124
+ // Library description for table entries
125
+ const LIBRARY_DESCRIPTION = 'Autogenerated by JLC-MCP';
126
+
127
+ /**
128
+ * Generate new sym-lib-table content with all JLC-MCP libraries
129
+ * Uses ${KICAD9_3RD_PARTY} variable for portable paths
130
+ */
131
+ function generateSymLibTable(): string {
132
+ const categories = getAllCategories();
133
+ let content = '(sym_lib_table\n (version 7)\n';
134
+
135
+ for (const category of categories) {
136
+ const name = `${LIBRARY_PREFIX}-${category}`;
137
+ const uri = getSymbolLibUri(category);
138
+ content += ` (lib (name "${name}")(type "KiCad")(uri "${uri}")(options "")(descr "${LIBRARY_DESCRIPTION}"))\n`;
139
+ }
140
+
141
+ content += ')\n';
142
+ return content;
143
+ }
144
+
145
+ /**
146
+ * Generate new fp-lib-table content with JLC-MCP footprint library
147
+ * Uses ${KICAD9_3RD_PARTY} variable for portable paths
148
+ */
149
+ function generateFpLibTable(): string {
150
+ const name = LIBRARY_PREFIX;
151
+ const uri = getFootprintLibUri();
152
+
153
+ return `(fp_lib_table
154
+ (version 7)
155
+ (lib (name "${name}")(type "KiCad")(uri "${uri}")(options "")(descr "${LIBRARY_DESCRIPTION}"))
156
+ )
157
+ `;
158
+ }
159
+
160
+ interface TableUpdateResult {
161
+ path: string;
162
+ created: boolean;
163
+ modified: boolean;
164
+ entriesAdded: number;
165
+ }
166
+
167
+ /**
168
+ * Ensure sym-lib-table has all JLC-MCP symbol libraries registered
169
+ */
170
+ async function ensureGlobalSymLibTable(version: string): Promise<TableUpdateResult> {
171
+ const configDir = getKicadConfigDir(version);
172
+ const tablePath = join(configDir, 'sym-lib-table');
173
+ const categories = getAllCategories();
174
+
175
+ // Ensure config directory exists
176
+ await mkdir(configDir, { recursive: true });
177
+
178
+ if (!existsSync(tablePath)) {
179
+ // Create new table with all libraries
180
+ const content = generateSymLibTable();
181
+ await writeFile(tablePath, content, 'utf-8');
182
+ return {
183
+ path: tablePath,
184
+ created: true,
185
+ modified: false,
186
+ entriesAdded: categories.length,
187
+ };
188
+ }
189
+
190
+ // Read existing table and add missing libraries
191
+ let content = await readFile(tablePath, 'utf-8');
192
+ let entriesAdded = 0;
193
+
194
+ for (const category of categories) {
195
+ const name = `${LIBRARY_PREFIX}-${category}`;
196
+
197
+ if (!libraryExistsInTable(content, name)) {
198
+ const uri = getSymbolLibUri(category);
199
+ content = addLibraryToTable(content, name, uri, 'sym', LIBRARY_DESCRIPTION);
200
+
201
+ // Validate entry was added correctly
202
+ if (!libraryExistsInTable(content, name)) {
203
+ throw new Error(`Failed to add symbol library ${name} to table`);
204
+ }
205
+ entriesAdded++;
206
+ }
207
+ }
208
+
209
+ if (entriesAdded > 0) {
210
+ await writeFile(tablePath, content, 'utf-8');
211
+ return {
212
+ path: tablePath,
213
+ created: false,
214
+ modified: true,
215
+ entriesAdded,
216
+ };
217
+ }
218
+
219
+ return {
220
+ path: tablePath,
221
+ created: false,
222
+ modified: false,
223
+ entriesAdded: 0,
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Ensure fp-lib-table has JLC-MCP footprint library registered
229
+ */
230
+ async function ensureGlobalFpLibTable(version: string): Promise<TableUpdateResult> {
231
+ const configDir = getKicadConfigDir(version);
232
+ const tablePath = join(configDir, 'fp-lib-table');
233
+ const name = LIBRARY_PREFIX;
234
+
235
+ // Ensure config directory exists
236
+ await mkdir(configDir, { recursive: true });
237
+
238
+ if (!existsSync(tablePath)) {
239
+ // Create new table
240
+ const content = generateFpLibTable();
241
+ await writeFile(tablePath, content, 'utf-8');
242
+ return {
243
+ path: tablePath,
244
+ created: true,
245
+ modified: false,
246
+ entriesAdded: 1,
247
+ };
248
+ }
249
+
250
+ // Read existing table and add if missing
251
+ let content = await readFile(tablePath, 'utf-8');
252
+
253
+ if (!libraryExistsInTable(content, name)) {
254
+ const uri = getFootprintLibUri();
255
+ content = addLibraryToTable(content, name, uri, 'fp', LIBRARY_DESCRIPTION);
256
+
257
+ // Validate entry was added correctly
258
+ if (!libraryExistsInTable(content, name)) {
259
+ throw new Error(`Failed to add footprint library ${name} to table`);
260
+ }
261
+
262
+ await writeFile(tablePath, content, 'utf-8');
263
+
264
+ return {
265
+ path: tablePath,
266
+ created: false,
267
+ modified: true,
268
+ entriesAdded: 1,
269
+ };
270
+ }
271
+
272
+ return {
273
+ path: tablePath,
274
+ created: false,
275
+ modified: false,
276
+ entriesAdded: 0,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Ensure library directories and empty stub files exist
282
+ * Creates structure under ~/Documents/KiCad/{version}/3rdparty/jlc_mcp/
283
+ */
284
+ async function ensureLibraryStubs(version: string): Promise<{
285
+ symbolsCreated: string[];
286
+ directoriesCreated: string[];
287
+ }> {
288
+ const baseDir = get3rdPartyBaseDir(version);
289
+ const symbolsDir = join(baseDir, 'symbols');
290
+ const footprintsDir = join(baseDir, 'footprints', getFootprintDirName());
291
+ const models3dDir = join(baseDir, '3dmodels', get3DModelsDirName());
292
+
293
+ const directoriesCreated: string[] = [];
294
+ const symbolsCreated: string[] = [];
295
+
296
+ // Create directories
297
+ for (const dir of [symbolsDir, footprintsDir, models3dDir]) {
298
+ if (!existsSync(dir)) {
299
+ await mkdir(dir, { recursive: true });
300
+ directoriesCreated.push(dir);
301
+ }
302
+ }
303
+
304
+ // Create empty symbol library files
305
+ const categories = getAllCategories();
306
+ const emptyContent = generateEmptySymbolLibrary();
307
+
308
+ for (const category of categories) {
309
+ const filePath = getSymbolLibPath(category, version);
310
+ if (!existsSync(filePath)) {
311
+ await writeFile(filePath, emptyContent, 'utf-8');
312
+ symbolsCreated.push(filePath);
313
+ }
314
+ }
315
+
316
+ return { symbolsCreated, directoriesCreated };
317
+ }
318
+
319
+ export interface GlobalRegistrationResult {
320
+ success: boolean;
321
+ version: string;
322
+ symLibTable: TableUpdateResult;
323
+ fpLibTable: TableUpdateResult;
324
+ libraryStubs: {
325
+ symbolsCreated: string[];
326
+ directoriesCreated: string[];
327
+ };
328
+ errors: string[];
329
+ }
330
+
331
+ /**
332
+ * Main entry point: Ensure JLC libraries are registered in KiCad global tables
333
+ * Call this on MCP server startup
334
+ */
335
+ export async function ensureGlobalLibraryTables(): Promise<GlobalRegistrationResult> {
336
+ const errors: string[] = [];
337
+ const version = detectKicadVersion();
338
+
339
+ let symLibTable: TableUpdateResult = {
340
+ path: '',
341
+ created: false,
342
+ modified: false,
343
+ entriesAdded: 0,
344
+ };
345
+
346
+ let fpLibTable: TableUpdateResult = {
347
+ path: '',
348
+ created: false,
349
+ modified: false,
350
+ entriesAdded: 0,
351
+ };
352
+
353
+ let libraryStubs = {
354
+ symbolsCreated: [] as string[],
355
+ directoriesCreated: [] as string[],
356
+ };
357
+
358
+ // Step 1: Create library stubs (directories and empty files)
359
+ try {
360
+ libraryStubs = await ensureLibraryStubs(version);
361
+ } catch (error) {
362
+ const msg = `Failed to create library stubs: ${error instanceof Error ? error.message : String(error)}`;
363
+ errors.push(msg);
364
+ // Continue anyway - tables might still work if directories exist
365
+ }
366
+
367
+ // Step 2: Update global sym-lib-table
368
+ try {
369
+ symLibTable = await ensureGlobalSymLibTable(version);
370
+ } catch (error) {
371
+ const msg = `Failed to update sym-lib-table: ${error instanceof Error ? error.message : String(error)}`;
372
+ errors.push(msg);
373
+ }
374
+
375
+ // Step 3: Update global fp-lib-table
376
+ try {
377
+ fpLibTable = await ensureGlobalFpLibTable(version);
378
+ } catch (error) {
379
+ const msg = `Failed to update fp-lib-table: ${error instanceof Error ? error.message : String(error)}`;
380
+ errors.push(msg);
381
+ }
382
+
383
+ // Success if at least the tables were updated (even if stubs failed)
384
+ const success = symLibTable.path !== '' && fpLibTable.path !== '' && errors.length === 0;
385
+
386
+ return {
387
+ success,
388
+ version,
389
+ symLibTable,
390
+ fpLibTable,
391
+ libraryStubs,
392
+ errors,
393
+ };
394
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * EasyEDA to KiCad converters
3
+ */
4
+
5
+ export { SymbolConverter, symbolConverter, type SymbolConversionOptions } from './symbol.js';
6
+ export { FootprintConverter, footprintConverter, type FootprintConversionOptions, type FootprintResult } from './footprint.js';
7
+ export {
8
+ ensureSymLibTable,
9
+ ensureFpLibTable,
10
+ getSymbolReference,
11
+ getFootprintReference,
12
+ libraryExistsInTable,
13
+ } from './lib-table.js';
14
+ export {
15
+ mapToKicadFootprint,
16
+ getKicadFootprintRef,
17
+ isStandardPassive,
18
+ type FootprintMapping,
19
+ } from './footprint-mapper.js';
20
+ export {
21
+ normalizeValue,
22
+ detectComponentType,
23
+ extractDisplayValue,
24
+ type ComponentType,
25
+ type NormalizedValue,
26
+ } from './value-normalizer.js';
27
+ export {
28
+ getLibraryCategory,
29
+ getLibraryFilename,
30
+ getFootprintDirName,
31
+ get3DModelsDirName,
32
+ getSymbolReference as getCategorySymbolReference,
33
+ getFootprintReference as getCategoryFootprintReference,
34
+ getAllCategories,
35
+ parseLibraryName,
36
+ type LibraryCategory,
37
+ } from './category-router.js';
38
+ export {
39
+ getSymbolTemplate,
40
+ hasFixedTemplate,
41
+ type SymbolTemplate,
42
+ } from './symbol-templates.js';
43
+ export {
44
+ ensureGlobalLibraryTables,
45
+ type GlobalRegistrationResult,
46
+ } from './global-lib-table.js';
@@ -0,0 +1,181 @@
1
+ /**
2
+ * KiCad Library Table Management
3
+ * Handles sym-lib-table and fp-lib-table generation and updates
4
+ */
5
+
6
+ import { readFile, writeFile } from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+
10
+ // Library configuration - matches category-router.ts LIBRARY_PREFIX
11
+ const DEFAULT_LIBRARY_NAME = 'JLC-MCP';
12
+ const DEFAULT_LIBRARY_DESCRIPTION = 'JLC-MCP Component Library (LCSC/EasyEDA)';
13
+
14
+ /**
15
+ * Generate sym-lib-table content
16
+ */
17
+ export function generateSymLibTable(
18
+ symbolLibPath: string,
19
+ libraryName: string = DEFAULT_LIBRARY_NAME,
20
+ description: string = DEFAULT_LIBRARY_DESCRIPTION
21
+ ): string {
22
+ return `(sym_lib_table
23
+ (version 7)
24
+ (lib (name "${libraryName}")(type "KiCad")(uri "${symbolLibPath}")(options "")(descr "${description}"))
25
+ )
26
+ `;
27
+ }
28
+
29
+ /**
30
+ * Generate fp-lib-table content
31
+ */
32
+ export function generateFpLibTable(
33
+ footprintLibPath: string,
34
+ libraryName: string = DEFAULT_LIBRARY_NAME,
35
+ description: string = DEFAULT_LIBRARY_DESCRIPTION
36
+ ): string {
37
+ return `(fp_lib_table
38
+ (version 7)
39
+ (lib (name "${libraryName}")(type "KiCad")(uri "${footprintLibPath}")(options "")(descr "${description}"))
40
+ )
41
+ `;
42
+ }
43
+
44
+ /**
45
+ * Check if a library entry exists in a lib-table file
46
+ */
47
+ export function libraryExistsInTable(tableContent: string, libraryName: string): boolean {
48
+ const pattern = new RegExp(`\\(name\\s+"${libraryName}"\\)`, 'm');
49
+ return pattern.test(tableContent);
50
+ }
51
+
52
+ /**
53
+ * Add a library entry to an existing lib-table content
54
+ */
55
+ export function addLibraryToTable(
56
+ tableContent: string,
57
+ libraryName: string,
58
+ libraryPath: string,
59
+ type: 'sym' | 'fp',
60
+ description: string = DEFAULT_LIBRARY_DESCRIPTION
61
+ ): string {
62
+ // Check if library already exists
63
+ if (libraryExistsInTable(tableContent, libraryName)) {
64
+ return tableContent; // No change needed
65
+ }
66
+
67
+ const trimmed = tableContent.trimEnd();
68
+ if (!trimmed.endsWith(')')) {
69
+ throw new Error('Invalid lib-table format: missing closing parenthesis');
70
+ }
71
+
72
+ // Remove the last closing paren
73
+ const withoutClose = trimmed.slice(0, -1);
74
+
75
+ // Add new library entry
76
+ const newEntry = ` (lib (name "${libraryName}")(type "KiCad")(uri "${libraryPath}")(options "")(descr "${description}"))\n`;
77
+
78
+ return withoutClose + newEntry + ')\n';
79
+ }
80
+
81
+ /**
82
+ * Ensure sym-lib-table exists and contains specified library
83
+ * @param projectDir - Path to the KiCad project directory
84
+ * @param symbolLibPath - Path to the symbol library file (relative or absolute)
85
+ * @param libraryName - Library name (default: LCSC)
86
+ * @param description - Library description
87
+ * @returns true if table was created or modified, false if already correct
88
+ */
89
+ export async function ensureSymLibTable(
90
+ projectDir: string,
91
+ symbolLibPath: string,
92
+ libraryName: string = DEFAULT_LIBRARY_NAME,
93
+ description: string = DEFAULT_LIBRARY_DESCRIPTION
94
+ ): Promise<{ created: boolean; modified: boolean; path: string }> {
95
+ const tablePath = join(projectDir, 'sym-lib-table');
96
+
97
+ // Use ${KIPRJMOD} for project-relative paths
98
+ const relativePath = symbolLibPath.startsWith(projectDir)
99
+ ? '${KIPRJMOD}' + symbolLibPath.slice(projectDir.length)
100
+ : symbolLibPath;
101
+
102
+ if (!existsSync(tablePath)) {
103
+ // Create new table
104
+ const content = generateSymLibTable(relativePath, libraryName, description);
105
+ await writeFile(tablePath, content, 'utf-8');
106
+ return { created: true, modified: false, path: tablePath };
107
+ }
108
+
109
+ // Read existing table
110
+ const existingContent = await readFile(tablePath, 'utf-8');
111
+
112
+ if (libraryExistsInTable(existingContent, libraryName)) {
113
+ return { created: false, modified: false, path: tablePath };
114
+ }
115
+
116
+ // Add library to existing table
117
+ const updatedContent = addLibraryToTable(existingContent, libraryName, relativePath, 'sym', description);
118
+ await writeFile(tablePath, updatedContent, 'utf-8');
119
+ return { created: false, modified: true, path: tablePath };
120
+ }
121
+
122
+ /**
123
+ * Ensure fp-lib-table exists and contains specified library
124
+ * @param projectDir - Path to the KiCad project directory
125
+ * @param footprintLibPath - Path to the footprint library directory (relative or absolute)
126
+ * @param libraryName - Library name (default: LCSC)
127
+ * @param description - Library description
128
+ * @returns true if table was created or modified, false if already correct
129
+ */
130
+ export async function ensureFpLibTable(
131
+ projectDir: string,
132
+ footprintLibPath: string,
133
+ libraryName: string = DEFAULT_LIBRARY_NAME,
134
+ description: string = DEFAULT_LIBRARY_DESCRIPTION
135
+ ): Promise<{ created: boolean; modified: boolean; path: string }> {
136
+ const tablePath = join(projectDir, 'fp-lib-table');
137
+
138
+ // Use ${KIPRJMOD} for project-relative paths
139
+ const relativePath = footprintLibPath.startsWith(projectDir)
140
+ ? '${KIPRJMOD}' + footprintLibPath.slice(projectDir.length)
141
+ : footprintLibPath;
142
+
143
+ if (!existsSync(tablePath)) {
144
+ // Create new table
145
+ const content = generateFpLibTable(relativePath, libraryName, description);
146
+ await writeFile(tablePath, content, 'utf-8');
147
+ return { created: true, modified: false, path: tablePath };
148
+ }
149
+
150
+ // Read existing table
151
+ const existingContent = await readFile(tablePath, 'utf-8');
152
+
153
+ if (libraryExistsInTable(existingContent, libraryName)) {
154
+ return { created: false, modified: false, path: tablePath };
155
+ }
156
+
157
+ // Add library to existing table
158
+ const updatedContent = addLibraryToTable(existingContent, libraryName, relativePath, 'fp', description);
159
+ await writeFile(tablePath, updatedContent, 'utf-8');
160
+ return { created: false, modified: true, path: tablePath };
161
+ }
162
+
163
+ /**
164
+ * Get the symbol reference string for use in schematics
165
+ * @param symbolName - The symbol name within the library
166
+ * @param libraryName - Library name (default: LCSC)
167
+ * @returns Full reference like "LCSC:SymbolName"
168
+ */
169
+ export function getSymbolReference(symbolName: string, libraryName: string = DEFAULT_LIBRARY_NAME): string {
170
+ return `${libraryName}:${symbolName}`;
171
+ }
172
+
173
+ /**
174
+ * Get the footprint reference string for use in symbols/schematics
175
+ * @param footprintName - The footprint filename (without .kicad_mod)
176
+ * @param libraryName - Library name (default: LCSC)
177
+ * @returns Full reference like "LCSC:FootprintName"
178
+ */
179
+ export function getFootprintReference(footprintName: string, libraryName: string = DEFAULT_LIBRARY_NAME): string {
180
+ return `${libraryName}:${footprintName}`;
181
+ }