kicad-sch-api 0.1.3__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.

Files changed (75) hide show
  1. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/PKG-INFO +1 -1
  2. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/cli.py +208 -8
  3. kicad_sch_api-0.1.5/kicad_sch_api/daemon.py +322 -0
  4. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/server.py +113 -1
  5. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/PKG-INFO +1 -1
  6. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/SOURCES.txt +1 -0
  7. kicad_sch_api-0.1.5/kicad_sch_api.egg-info/entry_points.txt +7 -0
  8. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/pyproject.toml +7 -1
  9. kicad_sch_api-0.1.3/kicad_sch_api.egg-info/entry_points.txt +0 -3
  10. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/dead-code-analysis.md +0 -0
  11. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/publish-pypi.md +0 -0
  12. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/review-implementation.md +0 -0
  13. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/run-tests.md +0 -0
  14. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-and-commit.md +0 -0
  15. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-memory-bank.md +0 -0
  16. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/.claude/commands/test/run-reference-tests.md +0 -0
  17. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/LICENSE +0 -0
  18. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/MANIFEST.in +0 -0
  19. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/README.md +0 -0
  20. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/examples/advanced_usage.py +0 -0
  21. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/examples/basic_usage.py +0 -0
  22. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/examples/mcp_basic_example.py +0 -0
  23. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/examples/mcp_integration.py +0 -0
  24. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/__init__.py +0 -0
  25. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/__init__.py +0 -0
  26. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/components.py +0 -0
  27. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/formatter.py +0 -0
  28. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/ic_manager.py +0 -0
  29. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/junctions.py +0 -0
  30. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/parser.py +0 -0
  31. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/schematic.py +0 -0
  32. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/types.py +0 -0
  33. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/core/wires.py +0 -0
  34. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/__init__.py +0 -0
  35. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/search_index.py +0 -0
  36. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/library/__init__.py +0 -0
  37. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/library/cache.py +0 -0
  38. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/__init__.py +0 -0
  39. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/py.typed +0 -0
  40. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/__init__.py +0 -0
  41. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/validation.py +0 -0
  42. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
  43. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/requires.txt +0 -0
  44. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/top_level.txt +0 -0
  45. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/setup.cfg +0 -0
  46. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
  47. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
  48. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
  49. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
  50. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
  51. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
  52. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
  53. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
  54. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
  55. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
  56. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
  57. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
  58. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
  59. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
  60. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
  61. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
  62. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
  63. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
  64. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
  65. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
  66. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
  67. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
  68. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
  69. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
  70. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
  71. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
  72. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
  73. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
  74. {kicad_sch_api-0.1.3 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
  75. {kicad_sch_api-0.1.3 → 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
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>
@@ -279,6 +279,133 @@ def show_logs():
279
279
  else:
280
280
  print(" ~/.local/share/Claude/logs/mcp-server-kicad-sch-api.log")
281
281
 
282
+ def setup_everything() -> bool:
283
+ """One-command setup that does everything automatically."""
284
+ print("🚀 KiCAD Schematic API - Complete Setup")
285
+ print("=" * 45)
286
+ print()
287
+
288
+ success = True
289
+
290
+ # 1. Test installation
291
+ print("Step 1/4: Testing installation...")
292
+ if not test_installation():
293
+ print("❌ Installation test failed. Please reinstall the package.")
294
+ return False
295
+ print()
296
+
297
+ # 2. Initialize cache
298
+ print("Step 2/4: Initializing component cache...")
299
+ if not init_cache():
300
+ print("⚠️ Cache initialization failed, but continuing...")
301
+ print()
302
+
303
+ # 3. Setup Claude Code
304
+ print("Step 3/4: Configuring Claude Code...")
305
+ if not setup_claude_code():
306
+ print("⚠️ Claude Code setup failed, but continuing...")
307
+ print()
308
+
309
+ # 4. Create demo
310
+ print("Step 4/4: Creating demo schematic...")
311
+ if not create_demo():
312
+ print("⚠️ Demo creation failed, but setup is complete")
313
+ print()
314
+
315
+ # Final status
316
+ print("🎉 Setup Complete!")
317
+ print()
318
+ print("Next steps:")
319
+ print("1. Restart Claude Code")
320
+ print("2. Try: 'Create a voltage divider with two 10kΩ resistors'")
321
+ print("3. Open demo_circuit.kicad_sch in KiCAD to see the example")
322
+ print()
323
+
324
+ return True
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
+
282
409
  def main():
283
410
  """Main CLI entry point."""
284
411
  parser = argparse.ArgumentParser(
@@ -286,19 +413,39 @@ def main():
286
413
  formatter_class=argparse.RawDescriptionHelpFormatter,
287
414
  epilog="""
288
415
  Examples:
289
- kicad-sch-api --setup-claude-code # Configure Claude Code MCP
290
- kicad-sch-api --test # Test installation
291
- kicad-sch-api --demo # Create demo schematic
292
- kicad-sch-api --status # Show status
416
+ kicad-sch-api --setup # Complete one-command setup (RECOMMENDED)
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
293
423
  """
294
424
  )
295
425
 
426
+ # Main setup options
427
+ parser.add_argument('--setup', action='store_true',
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
296
443
  parser.add_argument('--setup-claude-code', action='store_true',
297
- help='Configure Claude Code MCP settings automatically')
444
+ help='Configure Claude Code MCP settings only')
298
445
  parser.add_argument('--test', action='store_true',
299
446
  help='Test that the installation is working')
300
447
  parser.add_argument('--status', action='store_true',
301
- help='Show installation and configuration status')
448
+ help='Show detailed installation and configuration status')
302
449
  parser.add_argument('--demo', action='store_true',
303
450
  help='Create a demo schematic')
304
451
  parser.add_argument('--init-cache', action='store_true',
@@ -310,14 +457,67 @@ Examples:
310
457
 
311
458
  args = parser.parse_args()
312
459
 
313
- # If no arguments provided, show help
460
+ # If no arguments provided, suggest the daemon setup
314
461
  if not any(vars(args).values()):
315
- parser.print_help()
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")
466
+ print()
467
+ print("📖 For legacy setup:")
468
+ print(" kicad-sch-api --setup")
469
+ print()
470
+ print("🆘 For help with all options:")
471
+ print(" kicad-sch-api --help")
472
+ return
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
+
316
511
  return
317
512
 
318
513
  # Execute requested actions
319
514
  success = True
320
515
 
516
+ if args.setup_daemon:
517
+ success &= setup_daemon()
518
+ elif args.setup:
519
+ success &= setup_everything()
520
+
321
521
  if args.setup_claude_code:
322
522
  success &= setup_claude_code()
323
523
 
@@ -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.3")
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
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>
@@ -15,6 +15,7 @@ examples/mcp_basic_example.py
15
15
  examples/mcp_integration.py
16
16
  kicad_sch_api/__init__.py
17
17
  kicad_sch_api/cli.py
18
+ kicad_sch_api/daemon.py
18
19
  kicad_sch_api/py.typed
19
20
  kicad_sch_api.egg-info/PKG-INFO
20
21
  kicad_sch_api.egg-info/SOURCES.txt
@@ -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.3"
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",
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- kicad-sch-api = kicad_sch_api.cli:main
3
- kicad-sch-mcp = kicad_sch_api.mcp.server:main
File without changes
File without changes
File without changes
File without changes