kicad-sch-api 0.1.5__tar.gz → 0.1.6__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.5 → kicad_sch_api-0.1.6}/PKG-INFO +1 -1
  2. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/cli.py +33 -178
  3. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/mcp/server.py +2 -112
  4. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api.egg-info/PKG-INFO +1 -1
  5. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api.egg-info/SOURCES.txt +0 -1
  6. kicad_sch_api-0.1.6/kicad_sch_api.egg-info/entry_points.txt +3 -0
  7. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/pyproject.toml +1 -7
  8. kicad_sch_api-0.1.5/kicad_sch_api/daemon.py +0 -322
  9. kicad_sch_api-0.1.5/kicad_sch_api.egg-info/entry_points.txt +0 -7
  10. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/dead-code-analysis.md +0 -0
  11. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/publish-pypi.md +0 -0
  12. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/review-implementation.md +0 -0
  13. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/run-tests.md +0 -0
  14. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/update-and-commit.md +0 -0
  15. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/dev/update-memory-bank.md +0 -0
  16. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/.claude/commands/test/run-reference-tests.md +0 -0
  17. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/LICENSE +0 -0
  18. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/MANIFEST.in +0 -0
  19. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/README.md +0 -0
  20. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/examples/advanced_usage.py +0 -0
  21. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/examples/basic_usage.py +0 -0
  22. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/examples/mcp_basic_example.py +0 -0
  23. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/examples/mcp_integration.py +0 -0
  24. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/__init__.py +0 -0
  25. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/__init__.py +0 -0
  26. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/components.py +0 -0
  27. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/formatter.py +0 -0
  28. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/ic_manager.py +0 -0
  29. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/junctions.py +0 -0
  30. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/parser.py +0 -0
  31. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/schematic.py +0 -0
  32. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/types.py +0 -0
  33. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/core/wires.py +0 -0
  34. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/discovery/__init__.py +0 -0
  35. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/discovery/search_index.py +0 -0
  36. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/library/__init__.py +0 -0
  37. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/library/cache.py +0 -0
  38. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/mcp/__init__.py +0 -0
  39. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/py.typed +0 -0
  40. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/utils/__init__.py +0 -0
  41. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api/utils/validation.py +0 -0
  42. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
  43. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api.egg-info/requires.txt +0 -0
  44. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/kicad_sch_api.egg-info/top_level.txt +0 -0
  45. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/setup.cfg +0 -0
  46. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
  47. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
  48. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
  49. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
  50. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
  51. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
  52. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
  53. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
  54. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
  55. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
  56. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
  57. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
  58. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
  59. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
  60. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
  61. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
  62. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
  63. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
  64. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
  65. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
  66. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
  67. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
  68. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
  69. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
  70. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
  71. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
  72. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
  73. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
  74. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
  75. {kicad_sch_api-0.1.5 → kicad_sch_api-0.1.6}/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.5
3
+ Version: 0.1.6
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>
@@ -37,34 +37,22 @@ def setup_claude_code() -> bool:
37
37
  backup_path.write_text(config_path.read_text())
38
38
  print(f"📁 Backed up existing config to: {backup_path}")
39
39
 
40
- # Determine MCP command path
41
- mcp_command = os.environ.get('FOUND_MCP_PATH', 'kicad-sch-mcp')
40
+ # Read existing config or create new one
41
+ if config_path.exists():
42
+ with open(config_path) as f:
43
+ config = json.load(f)
44
+ else:
45
+ config = {}
42
46
 
43
- # If still using default, try to find the actual path
44
- if mcp_command == 'kicad-sch-mcp':
45
- python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
46
- possible_paths = [
47
- f"/Library/Frameworks/Python.framework/Versions/{python_version}/bin/kicad-sch-mcp",
48
- os.path.expanduser(f"~/Library/Python/{python_version}/bin/kicad-sch-mcp"),
49
- os.path.expanduser("~/.local/bin/kicad-sch-mcp"),
50
- "/usr/local/bin/kicad-sch-mcp"
51
- ]
52
-
53
- for path_to_try in possible_paths:
54
- if os.path.exists(path_to_try) and os.access(path_to_try, os.X_OK):
55
- mcp_command = path_to_try
56
- print(f"📍 Using MCP command at: {mcp_command}")
57
- break
47
+ # Ensure mcpServers section exists
48
+ if "mcpServers" not in config:
49
+ config["mcpServers"] = {}
58
50
 
59
- # Create new configuration
60
- config = {
61
- "mcpServers": {
62
- "kicad-sch-api": {
63
- "command": mcp_command,
64
- "args": [],
65
- "env": {}
66
- }
67
- }
51
+ # Use direct Python command - this is more reliable than trying to find binary paths
52
+ config["mcpServers"]["kicad-sch-api"] = {
53
+ "command": sys.executable,
54
+ "args": ["-m", "kicad_sch_api.mcp.server"],
55
+ "env": {}
68
56
  }
69
57
 
70
58
  # Write configuration
@@ -282,7 +270,7 @@ def show_logs():
282
270
  def setup_everything() -> bool:
283
271
  """One-command setup that does everything automatically."""
284
272
  print("🚀 KiCAD Schematic API - Complete Setup")
285
- print("=" * 45)
273
+ print("=" * 40)
286
274
  print()
287
275
 
288
276
  success = True
@@ -300,7 +288,7 @@ def setup_everything() -> bool:
300
288
  print("⚠️ Cache initialization failed, but continuing...")
301
289
  print()
302
290
 
303
- # 3. Setup Claude Code
291
+ # 3. Setup Claude Code (using direct Python command)
304
292
  print("Step 3/4: Configuring Claude Code...")
305
293
  if not setup_claude_code():
306
294
  print("⚠️ Claude Code setup failed, but continuing...")
@@ -315,97 +303,23 @@ def setup_everything() -> bool:
315
303
  # Final status
316
304
  print("🎉 Setup Complete!")
317
305
  print()
318
- print("Next steps:")
306
+ print(" What was configured:")
307
+ print(" • MCP server ready for Claude Code")
308
+ print(" • Component discovery cache initialized")
309
+ print(" • Demo schematic created")
310
+ print()
311
+ print("🚀 Next steps:")
319
312
  print("1. Restart Claude Code")
320
313
  print("2. Try: 'Create a voltage divider with two 10kΩ resistors'")
321
314
  print("3. Open demo_circuit.kicad_sch in KiCAD to see the example")
322
315
  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
316
+ print("🔍 To test manually:")
317
+ print(" python -m kicad_sch_api.mcp.server --test")
340
318
  print()
341
319
 
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
320
  return True
408
321
 
322
+
409
323
  def main():
410
324
  """Main CLI entry point."""
411
325
  parser = argparse.ArgumentParser(
@@ -413,11 +327,8 @@ def main():
413
327
  formatter_class=argparse.RawDescriptionHelpFormatter,
414
328
  epilog="""
415
329
  Examples:
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
330
+ kicad-sch-api --setup # Complete one-command setup (RECOMMENDED)
331
+ kicad-sch-api --setup-claude-code # Configure Claude Code MCP settings only
421
332
  kicad-sch-api --test # Test installation
422
333
  kicad-sch-api --demo # Create demo schematic
423
334
  """
@@ -425,21 +336,9 @@ Examples:
425
336
 
426
337
  # Main setup options
427
338
  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
339
+ help='Complete one-command setup (RECOMMENDED)')
340
+
341
+ # Setup options
443
342
  parser.add_argument('--setup-claude-code', action='store_true',
444
343
  help='Configure Claude Code MCP settings only')
445
344
  parser.add_argument('--test', action='store_true',
@@ -457,65 +356,21 @@ Examples:
457
356
 
458
357
  args = parser.parse_args()
459
358
 
460
- # If no arguments provided, suggest the daemon setup
359
+ # If no arguments provided, suggest setup
461
360
  if not any(vars(args).values()):
462
361
  print("🚀 KiCAD Schematic API - Command Line Interface")
463
362
  print()
464
- print("🌟 RECOMMENDED: Setup with daemon-style MCP server:")
465
- print(" kicad-sch-api --setup-daemon")
466
- print()
467
- print("📖 For legacy setup:")
363
+ print("🌟 RECOMMENDED: Complete setup:")
468
364
  print(" kicad-sch-api --setup")
469
365
  print()
470
366
  print("🆘 For help with all options:")
471
367
  print(" kicad-sch-api --help")
472
368
  return
473
369
 
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
-
513
370
  # Execute requested actions
514
371
  success = True
515
372
 
516
- if args.setup_daemon:
517
- success &= setup_daemon()
518
- elif args.setup:
373
+ if args.setup:
519
374
  success &= setup_everything()
520
375
 
521
376
  if args.setup_claude_code:
@@ -1457,28 +1457,17 @@ This hierarchical approach makes complex designs manageable and promotes reusabl
1457
1457
  """
1458
1458
 
1459
1459
  def main():
1460
- """Run the MCP server."""
1460
+ """Run the MCP server in standard on-demand mode."""
1461
1461
  import argparse
1462
- import os
1463
- import signal
1464
- import atexit
1465
- import time
1466
1462
 
1467
1463
  parser = argparse.ArgumentParser(description="KiCAD Schematic MCP Server")
1468
1464
  parser.add_argument('--test', action='store_true', help='Run quick test and exit')
1469
1465
  parser.add_argument('--debug', action='store_true', help='Enable debug logging')
1470
1466
  parser.add_argument('--status', action='store_true', help='Show server status and exit')
1471
1467
  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')
1475
1468
 
1476
1469
  args = parser.parse_args()
1477
1470
 
1478
- # Handle daemon mode
1479
- if args.daemon:
1480
- return run_daemon(args.log_file, args.pid_file, args.debug)
1481
-
1482
1471
  if args.debug:
1483
1472
  logging.getLogger().setLevel(logging.DEBUG)
1484
1473
  logger.debug("Debug logging enabled")
@@ -1509,6 +1498,7 @@ def main():
1509
1498
 
1510
1499
  logger.info("Starting KiCAD Schematic MCP Server...")
1511
1500
  try:
1501
+ # Run normally - Claude Desktop will manage the process lifecycle
1512
1502
  mcp.run()
1513
1503
  except KeyboardInterrupt:
1514
1504
  logger.info("Server stopped by user")
@@ -1516,106 +1506,6 @@ def main():
1516
1506
  logger.error(f"Server error: {e}")
1517
1507
  sys.exit(1)
1518
1508
 
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
1509
 
1620
1510
  if __name__ == "__main__":
1621
1511
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kicad-sch-api
3
- Version: 0.1.5
3
+ Version: 0.1.6
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,7 +15,6 @@ 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
19
18
  kicad_sch_api/py.typed
20
19
  kicad_sch_api.egg-info/PKG-INFO
21
20
  kicad_sch_api.egg-info/SOURCES.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ kicad-sch-api = kicad_sch_api.cli:main
3
+ 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.5"
7
+ version = "0.1.6"
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,12 +43,6 @@ 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
-
52
46
  [project.optional-dependencies]
53
47
  dev = [
54
48
  "pytest>=7.0.0",
@@ -1,322 +0,0 @@
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()
@@ -1,7 +0,0 @@
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
File without changes
File without changes
File without changes
File without changes