kicad-sch-api 0.1.4__tar.gz → 0.1.5__tar.gz
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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/PKG-INFO +1 -1
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/cli.py +153 -9
- kicad_sch_api-0.1.5/kicad_sch_api/daemon.py +322 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/server.py +113 -1
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/PKG-INFO +1 -1
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/SOURCES.txt +1 -0
- kicad_sch_api-0.1.5/kicad_sch_api.egg-info/entry_points.txt +7 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/pyproject.toml +7 -1
- kicad_sch_api-0.1.4/kicad_sch_api.egg-info/entry_points.txt +0 -3
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/dead-code-analysis.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/publish-pypi.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/review-implementation.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/run-tests.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-and-commit.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-memory-bank.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/test/run-reference-tests.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/LICENSE +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/MANIFEST.in +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/README.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/advanced_usage.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/basic_usage.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/mcp_basic_example.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/mcp_integration.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/components.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/formatter.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/ic_manager.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/junctions.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/parser.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/schematic.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/types.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/wires.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/search_index.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/library/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/library/cache.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/py.typed +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/__init__.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/validation.py +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/requires.txt +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/top_level.txt +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/setup.cfg +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
- {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_sch +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration
|
|
5
5
|
Author-email: Circuit-Synth <shane@circuit-synth.com>
|
|
6
6
|
Maintainer-email: Circuit-Synth <shane@circuit-synth.com>
|
|
@@ -323,6 +323,89 @@ def setup_everything() -> bool:
|
|
|
323
323
|
|
|
324
324
|
return True
|
|
325
325
|
|
|
326
|
+
def setup_daemon() -> bool:
|
|
327
|
+
"""Setup with daemon-style MCP server (RECOMMENDED)."""
|
|
328
|
+
print("🚀 KiCAD Schematic API - Daemon Setup")
|
|
329
|
+
print("=" * 50)
|
|
330
|
+
print("This will set up a persistent MCP daemon that runs in the background.")
|
|
331
|
+
print()
|
|
332
|
+
|
|
333
|
+
success = True
|
|
334
|
+
|
|
335
|
+
# 1. Test installation
|
|
336
|
+
print("Step 1/5: Testing installation...")
|
|
337
|
+
if not test_installation():
|
|
338
|
+
print("❌ Installation test failed. Please reinstall the package.")
|
|
339
|
+
return False
|
|
340
|
+
print()
|
|
341
|
+
|
|
342
|
+
# 2. Initialize cache
|
|
343
|
+
print("Step 2/5: Initializing component cache...")
|
|
344
|
+
if not init_cache():
|
|
345
|
+
print("⚠️ Cache initialization failed, but continuing...")
|
|
346
|
+
print()
|
|
347
|
+
|
|
348
|
+
# 3. Start daemon
|
|
349
|
+
print("Step 3/5: Starting MCP daemon...")
|
|
350
|
+
from .daemon import MCPDaemon
|
|
351
|
+
daemon = MCPDaemon()
|
|
352
|
+
|
|
353
|
+
if daemon.is_running():
|
|
354
|
+
print("✅ Daemon is already running")
|
|
355
|
+
else:
|
|
356
|
+
if not daemon.start():
|
|
357
|
+
print("❌ Failed to start daemon")
|
|
358
|
+
return False
|
|
359
|
+
print()
|
|
360
|
+
|
|
361
|
+
# 4. Configure Claude Code
|
|
362
|
+
print("Step 4/5: Configuring Claude Code...")
|
|
363
|
+
if not daemon._update_claude_config():
|
|
364
|
+
print("⚠️ Claude Code configuration failed, but daemon is running...")
|
|
365
|
+
else:
|
|
366
|
+
print("✅ Claude Code configured successfully")
|
|
367
|
+
print()
|
|
368
|
+
|
|
369
|
+
# 5. Create demo
|
|
370
|
+
print("Step 5/5: Creating demo schematic...")
|
|
371
|
+
if not create_demo():
|
|
372
|
+
print("⚠️ Demo creation failed, but setup is complete")
|
|
373
|
+
print()
|
|
374
|
+
|
|
375
|
+
# Final status
|
|
376
|
+
status = daemon.get_status()
|
|
377
|
+
print("🎉 Daemon Setup Complete!")
|
|
378
|
+
print()
|
|
379
|
+
print("✨ What's new with daemon mode:")
|
|
380
|
+
print(" • MCP server runs persistently in background")
|
|
381
|
+
print(" • No PATH issues or virtual environment problems")
|
|
382
|
+
print(" • Automatic startup after system reboot (if desired)")
|
|
383
|
+
print(" • Better performance and reliability")
|
|
384
|
+
print()
|
|
385
|
+
print("📊 Status:")
|
|
386
|
+
print(f" Daemon running: {'✅ Yes' if status['running'] else '❌ No'}")
|
|
387
|
+
print(f" Claude configured: {'✅ Yes' if status['claude_configured'] else '❌ No'}")
|
|
388
|
+
print(f" Log file: {status['log_file']}")
|
|
389
|
+
print()
|
|
390
|
+
|
|
391
|
+
if status['running'] and status['claude_configured']:
|
|
392
|
+
print("🚀 Next steps:")
|
|
393
|
+
print("1. Restart Claude Code")
|
|
394
|
+
print("2. Try: 'Create a voltage divider with two 10kΩ resistors'")
|
|
395
|
+
print("3. Open demo_circuit.kicad_sch in KiCAD to see the example")
|
|
396
|
+
print()
|
|
397
|
+
print("🔧 Daemon management:")
|
|
398
|
+
print(" kicad-sch-api --daemon-status # Check status")
|
|
399
|
+
print(" kicad-sch-api --stop-daemon # Stop daemon")
|
|
400
|
+
print(" kicad-sch-api --start-daemon # Start daemon")
|
|
401
|
+
print(" kicad-sch-api --restart-daemon # Restart daemon")
|
|
402
|
+
else:
|
|
403
|
+
print("⚠️ Setup incomplete. Check the status and try again.")
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
print()
|
|
407
|
+
return True
|
|
408
|
+
|
|
326
409
|
def main():
|
|
327
410
|
"""Main CLI entry point."""
|
|
328
411
|
parser = argparse.ArgumentParser(
|
|
@@ -331,15 +414,32 @@ def main():
|
|
|
331
414
|
epilog="""
|
|
332
415
|
Examples:
|
|
333
416
|
kicad-sch-api --setup # Complete one-command setup (RECOMMENDED)
|
|
334
|
-
kicad-sch-api --setup-
|
|
335
|
-
kicad-sch-api --
|
|
336
|
-
kicad-sch-api --
|
|
337
|
-
kicad-sch-api --status
|
|
417
|
+
kicad-sch-api --setup-daemon # Setup with daemon-style MCP server
|
|
418
|
+
kicad-sch-api --start-daemon # Start MCP daemon in background
|
|
419
|
+
kicad-sch-api --stop-daemon # Stop MCP daemon
|
|
420
|
+
kicad-sch-api --daemon-status # Show daemon status
|
|
421
|
+
kicad-sch-api --test # Test installation
|
|
422
|
+
kicad-sch-api --demo # Create demo schematic
|
|
338
423
|
"""
|
|
339
424
|
)
|
|
340
425
|
|
|
426
|
+
# Main setup options
|
|
341
427
|
parser.add_argument('--setup', action='store_true',
|
|
342
428
|
help='Complete one-command setup (RECOMMENDED for new users)')
|
|
429
|
+
parser.add_argument('--setup-daemon', action='store_true',
|
|
430
|
+
help='Setup with daemon-style MCP server (RECOMMENDED)')
|
|
431
|
+
|
|
432
|
+
# Daemon management
|
|
433
|
+
parser.add_argument('--start-daemon', action='store_true',
|
|
434
|
+
help='Start MCP daemon in background')
|
|
435
|
+
parser.add_argument('--stop-daemon', action='store_true',
|
|
436
|
+
help='Stop MCP daemon')
|
|
437
|
+
parser.add_argument('--restart-daemon', action='store_true',
|
|
438
|
+
help='Restart MCP daemon')
|
|
439
|
+
parser.add_argument('--daemon-status', action='store_true',
|
|
440
|
+
help='Show daemon status and logs')
|
|
441
|
+
|
|
442
|
+
# Legacy/manual setup options
|
|
343
443
|
parser.add_argument('--setup-claude-code', action='store_true',
|
|
344
444
|
help='Configure Claude Code MCP settings only')
|
|
345
445
|
parser.add_argument('--test', action='store_true',
|
|
@@ -357,21 +457,65 @@ Examples:
|
|
|
357
457
|
|
|
358
458
|
args = parser.parse_args()
|
|
359
459
|
|
|
360
|
-
# If no arguments provided, suggest the
|
|
460
|
+
# If no arguments provided, suggest the daemon setup
|
|
361
461
|
if not any(vars(args).values()):
|
|
362
|
-
print("KiCAD Schematic API - Command Line Interface")
|
|
462
|
+
print("🚀 KiCAD Schematic API - Command Line Interface")
|
|
463
|
+
print()
|
|
464
|
+
print("🌟 RECOMMENDED: Setup with daemon-style MCP server:")
|
|
465
|
+
print(" kicad-sch-api --setup-daemon")
|
|
363
466
|
print()
|
|
364
|
-
print("For
|
|
467
|
+
print("📖 For legacy setup:")
|
|
365
468
|
print(" kicad-sch-api --setup")
|
|
366
469
|
print()
|
|
367
|
-
print("For help with all options:")
|
|
470
|
+
print("🆘 For help with all options:")
|
|
368
471
|
print(" kicad-sch-api --help")
|
|
369
472
|
return
|
|
370
473
|
|
|
474
|
+
# Import daemon management after args check
|
|
475
|
+
from .daemon import MCPDaemon
|
|
476
|
+
|
|
477
|
+
# Handle daemon commands
|
|
478
|
+
daemon = MCPDaemon()
|
|
479
|
+
|
|
480
|
+
if args.start_daemon:
|
|
481
|
+
success = daemon.start()
|
|
482
|
+
sys.exit(0 if success else 1)
|
|
483
|
+
|
|
484
|
+
if args.stop_daemon:
|
|
485
|
+
success = daemon.stop()
|
|
486
|
+
sys.exit(0 if success else 1)
|
|
487
|
+
|
|
488
|
+
if args.restart_daemon:
|
|
489
|
+
success = daemon.restart()
|
|
490
|
+
sys.exit(0 if success else 1)
|
|
491
|
+
|
|
492
|
+
if args.daemon_status:
|
|
493
|
+
status = daemon.get_status()
|
|
494
|
+
print(f"🚀 KiCAD Schematic MCP Server Status")
|
|
495
|
+
print("=" * 40)
|
|
496
|
+
print(f"Running: {'✅ Yes' if status['running'] else '❌ No'}")
|
|
497
|
+
|
|
498
|
+
if status["pid"]:
|
|
499
|
+
print(f"PID: {status['pid']}")
|
|
500
|
+
|
|
501
|
+
print(f"Log file: {status['log_file']}")
|
|
502
|
+
print(f"Claude configured: {'✅ Yes' if status['claude_configured'] else '❌ No'}")
|
|
503
|
+
|
|
504
|
+
if not status["claude_configured"]:
|
|
505
|
+
print("\n⚠️ Claude Code not configured. Run with --setup-daemon to fix.")
|
|
506
|
+
|
|
507
|
+
if status["running"]:
|
|
508
|
+
print("\n📜 Recent logs:")
|
|
509
|
+
daemon.show_logs(10)
|
|
510
|
+
|
|
511
|
+
return
|
|
512
|
+
|
|
371
513
|
# Execute requested actions
|
|
372
514
|
success = True
|
|
373
515
|
|
|
374
|
-
if args.
|
|
516
|
+
if args.setup_daemon:
|
|
517
|
+
success &= setup_daemon()
|
|
518
|
+
elif args.setup:
|
|
375
519
|
success &= setup_everything()
|
|
376
520
|
|
|
377
521
|
if args.setup_claude_code:
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KiCAD Schematic API - MCP Server Daemon
|
|
4
|
+
|
|
5
|
+
Provides daemon-style MCP server with proper process management.
|
|
6
|
+
Users can start/stop/restart the server as a background process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import signal
|
|
12
|
+
import subprocess
|
|
13
|
+
import time
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
import tempfile
|
|
19
|
+
|
|
20
|
+
# Setup logging
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
level=logging.INFO,
|
|
23
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
24
|
+
)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
class MCPDaemon:
|
|
28
|
+
"""Manages the MCP server as a daemon process."""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.name = "kicad-sch-mcp"
|
|
32
|
+
self.home_dir = Path.home()
|
|
33
|
+
self.config_dir = self.home_dir / ".kicad-sch-api"
|
|
34
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
35
|
+
|
|
36
|
+
# Daemon files
|
|
37
|
+
self.pid_file = self.config_dir / "mcp-daemon.pid"
|
|
38
|
+
self.log_file = self.config_dir / "mcp-daemon.log"
|
|
39
|
+
self.socket_file = self.config_dir / "mcp-daemon.sock"
|
|
40
|
+
|
|
41
|
+
# Claude configuration
|
|
42
|
+
self.claude_config = self._get_claude_config_path()
|
|
43
|
+
|
|
44
|
+
def _get_claude_config_path(self) -> Path:
|
|
45
|
+
"""Get the Claude Code configuration file path for current platform."""
|
|
46
|
+
if sys.platform == "darwin":
|
|
47
|
+
return self.home_dir / "Library/Application Support/Claude/claude_desktop_config.json"
|
|
48
|
+
elif sys.platform == "win32":
|
|
49
|
+
return Path(os.environ["APPDATA"]) / "Claude/claude_desktop_config.json"
|
|
50
|
+
else: # Linux and others
|
|
51
|
+
return self.home_dir / ".config/Claude/claude_desktop_config.json"
|
|
52
|
+
|
|
53
|
+
def is_running(self) -> bool:
|
|
54
|
+
"""Check if daemon is currently running."""
|
|
55
|
+
if not self.pid_file.exists():
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with open(self.pid_file) as f:
|
|
60
|
+
pid = int(f.read().strip())
|
|
61
|
+
|
|
62
|
+
# Check if process is still alive
|
|
63
|
+
os.kill(pid, 0)
|
|
64
|
+
return True
|
|
65
|
+
except (ValueError, ProcessLookupError, OSError):
|
|
66
|
+
# PID file exists but process is dead, clean up
|
|
67
|
+
self.pid_file.unlink(missing_ok=True)
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def get_status(self) -> dict:
|
|
71
|
+
"""Get detailed daemon status."""
|
|
72
|
+
status = {
|
|
73
|
+
"running": self.is_running(),
|
|
74
|
+
"pid": None,
|
|
75
|
+
"log_file": str(self.log_file),
|
|
76
|
+
"socket_file": str(self.socket_file),
|
|
77
|
+
"claude_configured": self._check_claude_config()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if status["running"] and self.pid_file.exists():
|
|
81
|
+
try:
|
|
82
|
+
with open(self.pid_file) as f:
|
|
83
|
+
status["pid"] = int(f.read().strip())
|
|
84
|
+
except (ValueError, OSError):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
return status
|
|
88
|
+
|
|
89
|
+
def _check_claude_config(self) -> bool:
|
|
90
|
+
"""Check if Claude Code is configured with our MCP server."""
|
|
91
|
+
if not self.claude_config.exists():
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
with open(self.claude_config) as f:
|
|
96
|
+
config = json.load(f)
|
|
97
|
+
|
|
98
|
+
servers = config.get("mcpServers", {})
|
|
99
|
+
return "kicad-sch-api" in servers
|
|
100
|
+
except (json.JSONDecodeError, OSError):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def start(self) -> bool:
|
|
104
|
+
"""Start the daemon process."""
|
|
105
|
+
if self.is_running():
|
|
106
|
+
logger.info("Daemon is already running")
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
logger.info("Starting MCP daemon...")
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Create the daemon process using the MCP server entry point
|
|
113
|
+
cmd = [
|
|
114
|
+
sys.executable, "-m", "kicad_sch_api.mcp.server",
|
|
115
|
+
"--daemon",
|
|
116
|
+
"--log-file", str(self.log_file),
|
|
117
|
+
"--pid-file", str(self.pid_file)
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
# Start daemon process
|
|
121
|
+
process = subprocess.Popen(
|
|
122
|
+
cmd,
|
|
123
|
+
stdout=subprocess.DEVNULL,
|
|
124
|
+
stderr=subprocess.DEVNULL,
|
|
125
|
+
stdin=subprocess.DEVNULL,
|
|
126
|
+
start_new_session=True
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Give it a moment to start
|
|
130
|
+
time.sleep(2)
|
|
131
|
+
|
|
132
|
+
# Check if it started successfully
|
|
133
|
+
if self.is_running():
|
|
134
|
+
logger.info(f"Daemon started successfully (PID: {process.pid})")
|
|
135
|
+
self._update_claude_config()
|
|
136
|
+
return True
|
|
137
|
+
else:
|
|
138
|
+
logger.error("Failed to start daemon")
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"Error starting daemon: {e}")
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def stop(self) -> bool:
|
|
146
|
+
"""Stop the daemon process."""
|
|
147
|
+
if not self.is_running():
|
|
148
|
+
logger.info("Daemon is not running")
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
with open(self.pid_file) as f:
|
|
153
|
+
pid = int(f.read().strip())
|
|
154
|
+
|
|
155
|
+
logger.info(f"Stopping daemon (PID: {pid})...")
|
|
156
|
+
|
|
157
|
+
# Try graceful shutdown first
|
|
158
|
+
os.kill(pid, signal.SIGTERM)
|
|
159
|
+
|
|
160
|
+
# Wait for graceful shutdown
|
|
161
|
+
for _ in range(10):
|
|
162
|
+
if not self.is_running():
|
|
163
|
+
break
|
|
164
|
+
time.sleep(0.5)
|
|
165
|
+
|
|
166
|
+
# Force kill if still running
|
|
167
|
+
if self.is_running():
|
|
168
|
+
logger.warning("Forcing daemon shutdown...")
|
|
169
|
+
os.kill(pid, signal.SIGKILL)
|
|
170
|
+
time.sleep(1)
|
|
171
|
+
|
|
172
|
+
# Clean up files
|
|
173
|
+
self.pid_file.unlink(missing_ok=True)
|
|
174
|
+
self.socket_file.unlink(missing_ok=True)
|
|
175
|
+
|
|
176
|
+
if self.is_running():
|
|
177
|
+
logger.error("Failed to stop daemon")
|
|
178
|
+
return False
|
|
179
|
+
else:
|
|
180
|
+
logger.info("Daemon stopped successfully")
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
except (ValueError, ProcessLookupError, OSError) as e:
|
|
184
|
+
logger.error(f"Error stopping daemon: {e}")
|
|
185
|
+
# Clean up stale files
|
|
186
|
+
self.pid_file.unlink(missing_ok=True)
|
|
187
|
+
self.socket_file.unlink(missing_ok=True)
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
def restart(self) -> bool:
|
|
191
|
+
"""Restart the daemon process."""
|
|
192
|
+
logger.info("Restarting daemon...")
|
|
193
|
+
self.stop()
|
|
194
|
+
time.sleep(1)
|
|
195
|
+
return self.start()
|
|
196
|
+
|
|
197
|
+
def _update_claude_config(self) -> bool:
|
|
198
|
+
"""Update Claude Code configuration with daemon socket."""
|
|
199
|
+
try:
|
|
200
|
+
# Create directory if it doesn't exist
|
|
201
|
+
self.claude_config.parent.mkdir(parents=True, exist_ok=True)
|
|
202
|
+
|
|
203
|
+
# Read existing config or create new one
|
|
204
|
+
if self.claude_config.exists():
|
|
205
|
+
with open(self.claude_config) as f:
|
|
206
|
+
config = json.load(f)
|
|
207
|
+
else:
|
|
208
|
+
config = {}
|
|
209
|
+
|
|
210
|
+
# Ensure mcpServers section exists
|
|
211
|
+
if "mcpServers" not in config:
|
|
212
|
+
config["mcpServers"] = {}
|
|
213
|
+
|
|
214
|
+
# Update our server configuration
|
|
215
|
+
config["mcpServers"]["kicad-sch-api"] = {
|
|
216
|
+
"command": sys.executable,
|
|
217
|
+
"args": ["-m", "kicad_sch_api.mcp.server"],
|
|
218
|
+
"env": {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Write configuration back
|
|
222
|
+
with open(self.claude_config, 'w') as f:
|
|
223
|
+
json.dump(config, f, indent=2)
|
|
224
|
+
|
|
225
|
+
logger.info(f"Updated Claude configuration: {self.claude_config}")
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.error(f"Failed to update Claude configuration: {e}")
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def show_logs(self, lines: int = 20) -> None:
|
|
233
|
+
"""Show recent daemon log entries."""
|
|
234
|
+
if not self.log_file.exists():
|
|
235
|
+
print("No log file found")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Use tail-like functionality
|
|
240
|
+
with open(self.log_file) as f:
|
|
241
|
+
log_lines = f.readlines()
|
|
242
|
+
|
|
243
|
+
# Show last N lines
|
|
244
|
+
for line in log_lines[-lines:]:
|
|
245
|
+
print(line.rstrip())
|
|
246
|
+
|
|
247
|
+
except OSError as e:
|
|
248
|
+
logger.error(f"Error reading log file: {e}")
|
|
249
|
+
|
|
250
|
+
# CLI functions for entry points
|
|
251
|
+
def start_daemon():
|
|
252
|
+
"""Entry point for starting daemon."""
|
|
253
|
+
daemon = MCPDaemon()
|
|
254
|
+
success = daemon.start()
|
|
255
|
+
sys.exit(0 if success else 1)
|
|
256
|
+
|
|
257
|
+
def stop_daemon():
|
|
258
|
+
"""Entry point for stopping daemon."""
|
|
259
|
+
daemon = MCPDaemon()
|
|
260
|
+
success = daemon.stop()
|
|
261
|
+
sys.exit(0 if success else 1)
|
|
262
|
+
|
|
263
|
+
def restart_daemon():
|
|
264
|
+
"""Entry point for restarting daemon."""
|
|
265
|
+
daemon = MCPDaemon()
|
|
266
|
+
success = daemon.restart()
|
|
267
|
+
sys.exit(0 if success else 1)
|
|
268
|
+
|
|
269
|
+
def status_daemon():
|
|
270
|
+
"""Entry point for daemon status."""
|
|
271
|
+
daemon = MCPDaemon()
|
|
272
|
+
status = daemon.get_status()
|
|
273
|
+
|
|
274
|
+
print(f"🚀 KiCAD Schematic MCP Server Status")
|
|
275
|
+
print("=" * 40)
|
|
276
|
+
print(f"Running: {'✅ Yes' if status['running'] else '❌ No'}")
|
|
277
|
+
|
|
278
|
+
if status["pid"]:
|
|
279
|
+
print(f"PID: {status['pid']}")
|
|
280
|
+
|
|
281
|
+
print(f"Log file: {status['log_file']}")
|
|
282
|
+
print(f"Claude configured: {'✅ Yes' if status['claude_configured'] else '❌ No'}")
|
|
283
|
+
|
|
284
|
+
if not status["claude_configured"]:
|
|
285
|
+
print("\n⚠️ Claude Code not configured. Run with --configure-claude to fix.")
|
|
286
|
+
|
|
287
|
+
if status["running"]:
|
|
288
|
+
print("\n📜 Recent logs:")
|
|
289
|
+
daemon.show_logs(10)
|
|
290
|
+
|
|
291
|
+
def main():
|
|
292
|
+
"""Main daemon management interface."""
|
|
293
|
+
import argparse
|
|
294
|
+
|
|
295
|
+
parser = argparse.ArgumentParser(description="KiCAD Schematic MCP Daemon Manager")
|
|
296
|
+
parser.add_argument("--start", action="store_true", help="Start the daemon")
|
|
297
|
+
parser.add_argument("--stop", action="store_true", help="Stop the daemon")
|
|
298
|
+
parser.add_argument("--restart", action="store_true", help="Restart the daemon")
|
|
299
|
+
parser.add_argument("--status", action="store_true", help="Show daemon status")
|
|
300
|
+
parser.add_argument("--logs", type=int, default=20, help="Show recent log lines")
|
|
301
|
+
|
|
302
|
+
args = parser.parse_args()
|
|
303
|
+
|
|
304
|
+
daemon = MCPDaemon()
|
|
305
|
+
|
|
306
|
+
if args.start:
|
|
307
|
+
success = daemon.start()
|
|
308
|
+
sys.exit(0 if success else 1)
|
|
309
|
+
elif args.stop:
|
|
310
|
+
success = daemon.stop()
|
|
311
|
+
sys.exit(0 if success else 1)
|
|
312
|
+
elif args.restart:
|
|
313
|
+
success = daemon.restart()
|
|
314
|
+
sys.exit(0 if success else 1)
|
|
315
|
+
elif args.status:
|
|
316
|
+
status_daemon()
|
|
317
|
+
else:
|
|
318
|
+
# No arguments, show status
|
|
319
|
+
status_daemon()
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
main()
|
|
@@ -1459,21 +1459,32 @@ This hierarchical approach makes complex designs manageable and promotes reusabl
|
|
|
1459
1459
|
def main():
|
|
1460
1460
|
"""Run the MCP server."""
|
|
1461
1461
|
import argparse
|
|
1462
|
+
import os
|
|
1463
|
+
import signal
|
|
1464
|
+
import atexit
|
|
1465
|
+
import time
|
|
1462
1466
|
|
|
1463
1467
|
parser = argparse.ArgumentParser(description="KiCAD Schematic MCP Server")
|
|
1464
1468
|
parser.add_argument('--test', action='store_true', help='Run quick test and exit')
|
|
1465
1469
|
parser.add_argument('--debug', action='store_true', help='Enable debug logging')
|
|
1466
1470
|
parser.add_argument('--status', action='store_true', help='Show server status and exit')
|
|
1467
1471
|
parser.add_argument('--version', action='store_true', help='Show version and exit')
|
|
1472
|
+
parser.add_argument('--daemon', action='store_true', help='Run as daemon process')
|
|
1473
|
+
parser.add_argument('--log-file', type=str, help='Log file path for daemon mode')
|
|
1474
|
+
parser.add_argument('--pid-file', type=str, help='PID file path for daemon mode')
|
|
1468
1475
|
|
|
1469
1476
|
args = parser.parse_args()
|
|
1470
1477
|
|
|
1478
|
+
# Handle daemon mode
|
|
1479
|
+
if args.daemon:
|
|
1480
|
+
return run_daemon(args.log_file, args.pid_file, args.debug)
|
|
1481
|
+
|
|
1471
1482
|
if args.debug:
|
|
1472
1483
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
1473
1484
|
logger.debug("Debug logging enabled")
|
|
1474
1485
|
|
|
1475
1486
|
if args.version:
|
|
1476
|
-
print("KiCAD Schematic MCP Server v0.1.
|
|
1487
|
+
print("KiCAD Schematic MCP Server v0.1.5")
|
|
1477
1488
|
return
|
|
1478
1489
|
|
|
1479
1490
|
if args.status:
|
|
@@ -1505,5 +1516,106 @@ def main():
|
|
|
1505
1516
|
logger.error(f"Server error: {e}")
|
|
1506
1517
|
sys.exit(1)
|
|
1507
1518
|
|
|
1519
|
+
def run_daemon(log_file: Optional[str] = None, pid_file: Optional[str] = None, debug: bool = False):
|
|
1520
|
+
"""Run the MCP server as a daemon process."""
|
|
1521
|
+
import os
|
|
1522
|
+
import sys
|
|
1523
|
+
import signal
|
|
1524
|
+
import atexit
|
|
1525
|
+
from pathlib import Path
|
|
1526
|
+
|
|
1527
|
+
# Default paths
|
|
1528
|
+
if not log_file:
|
|
1529
|
+
log_file = str(Path.home() / ".kicad-sch-api" / "mcp-daemon.log")
|
|
1530
|
+
if not pid_file:
|
|
1531
|
+
pid_file = str(Path.home() / ".kicad-sch-api" / "mcp-daemon.pid")
|
|
1532
|
+
|
|
1533
|
+
# Ensure directory exists
|
|
1534
|
+
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
|
|
1535
|
+
Path(pid_file).parent.mkdir(parents=True, exist_ok=True)
|
|
1536
|
+
|
|
1537
|
+
# Daemonize process
|
|
1538
|
+
try:
|
|
1539
|
+
pid = os.fork()
|
|
1540
|
+
if pid > 0:
|
|
1541
|
+
# Exit parent process
|
|
1542
|
+
sys.exit(0)
|
|
1543
|
+
except OSError as e:
|
|
1544
|
+
logger.error(f"Fork failed: {e}")
|
|
1545
|
+
sys.exit(1)
|
|
1546
|
+
|
|
1547
|
+
# Decouple from parent environment
|
|
1548
|
+
os.chdir("/")
|
|
1549
|
+
os.setsid()
|
|
1550
|
+
os.umask(0)
|
|
1551
|
+
|
|
1552
|
+
# Second fork
|
|
1553
|
+
try:
|
|
1554
|
+
pid = os.fork()
|
|
1555
|
+
if pid > 0:
|
|
1556
|
+
# Exit first child
|
|
1557
|
+
sys.exit(0)
|
|
1558
|
+
except OSError as e:
|
|
1559
|
+
logger.error(f"Second fork failed: {e}")
|
|
1560
|
+
sys.exit(1)
|
|
1561
|
+
|
|
1562
|
+
# Redirect standard file descriptors
|
|
1563
|
+
sys.stdout.flush()
|
|
1564
|
+
sys.stderr.flush()
|
|
1565
|
+
|
|
1566
|
+
# Redirect to log file
|
|
1567
|
+
with open(log_file, 'a') as f:
|
|
1568
|
+
os.dup2(f.fileno(), sys.stdout.fileno())
|
|
1569
|
+
os.dup2(f.fileno(), sys.stderr.fileno())
|
|
1570
|
+
|
|
1571
|
+
# Close stdin
|
|
1572
|
+
with open(os.devnull, 'r') as f:
|
|
1573
|
+
os.dup2(f.fileno(), sys.stdin.fileno())
|
|
1574
|
+
|
|
1575
|
+
# Write PID file
|
|
1576
|
+
with open(pid_file, 'w') as f:
|
|
1577
|
+
f.write(str(os.getpid()))
|
|
1578
|
+
|
|
1579
|
+
# Setup signal handlers for graceful shutdown
|
|
1580
|
+
def cleanup():
|
|
1581
|
+
"""Clean up PID file on exit."""
|
|
1582
|
+
try:
|
|
1583
|
+
os.unlink(pid_file)
|
|
1584
|
+
except OSError:
|
|
1585
|
+
pass
|
|
1586
|
+
|
|
1587
|
+
def signal_handler(signum, frame):
|
|
1588
|
+
"""Handle shutdown signals."""
|
|
1589
|
+
logger.info(f"Received signal {signum}, shutting down daemon...")
|
|
1590
|
+
cleanup()
|
|
1591
|
+
sys.exit(0)
|
|
1592
|
+
|
|
1593
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
1594
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
1595
|
+
atexit.register(cleanup)
|
|
1596
|
+
|
|
1597
|
+
# Configure logging for daemon
|
|
1598
|
+
log_handler = logging.FileHandler(log_file)
|
|
1599
|
+
log_handler.setFormatter(logging.Formatter(
|
|
1600
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
1601
|
+
))
|
|
1602
|
+
|
|
1603
|
+
# Remove existing handlers and add file handler
|
|
1604
|
+
logger.handlers.clear()
|
|
1605
|
+
logger.addHandler(log_handler)
|
|
1606
|
+
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
1607
|
+
|
|
1608
|
+
# Start the MCP server
|
|
1609
|
+
logger.info("Starting KiCAD Schematic MCP Server daemon...")
|
|
1610
|
+
logger.info(f"PID: {os.getpid()}")
|
|
1611
|
+
logger.info(f"Log file: {log_file}")
|
|
1612
|
+
|
|
1613
|
+
try:
|
|
1614
|
+
mcp.run()
|
|
1615
|
+
except Exception as e:
|
|
1616
|
+
logger.error(f"Daemon error: {e}")
|
|
1617
|
+
cleanup()
|
|
1618
|
+
sys.exit(1)
|
|
1619
|
+
|
|
1508
1620
|
if __name__ == "__main__":
|
|
1509
1621
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration
|
|
5
5
|
Author-email: Circuit-Synth <shane@circuit-synth.com>
|
|
6
6
|
Maintainer-email: Circuit-Synth <shane@circuit-synth.com>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
kicad-sch-api = kicad_sch_api.cli:main
|
|
3
|
+
kicad-sch-daemon = kicad_sch_api.daemon:main
|
|
4
|
+
kicad-sch-daemon-start = kicad_sch_api.daemon:start_daemon
|
|
5
|
+
kicad-sch-daemon-status = kicad_sch_api.daemon:status_daemon
|
|
6
|
+
kicad-sch-daemon-stop = kicad_sch_api.daemon:stop_daemon
|
|
7
|
+
kicad-sch-mcp = kicad_sch_api.mcp.server:main
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kicad-sch-api"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Professional KiCAD schematic manipulation library with exact format preservation and AI agent integration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -43,6 +43,12 @@ dependencies = [
|
|
|
43
43
|
kicad-sch-api = "kicad_sch_api.cli:main"
|
|
44
44
|
kicad-sch-mcp = "kicad_sch_api.mcp.server:main"
|
|
45
45
|
|
|
46
|
+
# New daemon-style entry points
|
|
47
|
+
kicad-sch-daemon = "kicad_sch_api.daemon:main"
|
|
48
|
+
kicad-sch-daemon-start = "kicad_sch_api.daemon:start_daemon"
|
|
49
|
+
kicad-sch-daemon-stop = "kicad_sch_api.daemon:stop_daemon"
|
|
50
|
+
kicad-sch-daemon-status = "kicad_sch_api.daemon:status_daemon"
|
|
51
|
+
|
|
46
52
|
[project.optional-dependencies]
|
|
47
53
|
dev = [
|
|
48
54
|
"pytest>=7.0.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|