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.

Files changed (75) hide show
  1. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/PKG-INFO +1 -1
  2. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/cli.py +153 -9
  3. kicad_sch_api-0.1.5/kicad_sch_api/daemon.py +322 -0
  4. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/server.py +113 -1
  5. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/PKG-INFO +1 -1
  6. {kicad_sch_api-0.1.4 → 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.4 → kicad_sch_api-0.1.5}/pyproject.toml +7 -1
  9. kicad_sch_api-0.1.4/kicad_sch_api.egg-info/entry_points.txt +0 -3
  10. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/dead-code-analysis.md +0 -0
  11. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/publish-pypi.md +0 -0
  12. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/review-implementation.md +0 -0
  13. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/run-tests.md +0 -0
  14. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-and-commit.md +0 -0
  15. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/dev/update-memory-bank.md +0 -0
  16. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/.claude/commands/test/run-reference-tests.md +0 -0
  17. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/LICENSE +0 -0
  18. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/MANIFEST.in +0 -0
  19. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/README.md +0 -0
  20. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/advanced_usage.py +0 -0
  21. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/basic_usage.py +0 -0
  22. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/mcp_basic_example.py +0 -0
  23. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/examples/mcp_integration.py +0 -0
  24. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/__init__.py +0 -0
  25. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/__init__.py +0 -0
  26. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/components.py +0 -0
  27. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/formatter.py +0 -0
  28. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/ic_manager.py +0 -0
  29. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/junctions.py +0 -0
  30. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/parser.py +0 -0
  31. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/schematic.py +0 -0
  32. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/types.py +0 -0
  33. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/core/wires.py +0 -0
  34. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/__init__.py +0 -0
  35. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/discovery/search_index.py +0 -0
  36. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/library/__init__.py +0 -0
  37. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/library/cache.py +0 -0
  38. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/mcp/__init__.py +0 -0
  39. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/py.typed +0 -0
  40. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/__init__.py +0 -0
  41. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api/utils/validation.py +0 -0
  42. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
  43. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/requires.txt +0 -0
  44. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/kicad_sch_api.egg-info/top_level.txt +0 -0
  45. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/setup.cfg +0 -0
  46. {kicad_sch_api-0.1.4 → kicad_sch_api-0.1.5}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {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
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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.4
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-claude-code # Configure Claude Code MCP only
335
- kicad-sch-api --test # Test installation
336
- kicad-sch-api --demo # Create demo schematic
337
- kicad-sch-api --status # Show detailed 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 simple setup
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 new users, run the complete setup:")
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.setup:
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.4")
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.4
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.4"
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