agent-rules-sync 1.4.3__tar.gz → 1.4.4__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.
Files changed (22) hide show
  1. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/PKG-INFO +1 -1
  2. agent_rules_sync-1.4.4/agent_guardian.py +100 -0
  3. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/PKG-INFO +1 -1
  4. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/SOURCES.txt +1 -0
  5. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/top_level.txt +1 -0
  6. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.py +13 -1
  7. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/install_daemon.py +38 -20
  8. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/pyproject.toml +2 -2
  9. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/LICENSE +0 -0
  10. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/README.md +0 -0
  11. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/dependency_links.txt +0 -0
  12. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/entry_points.txt +0 -0
  13. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_settings_sync.py +0 -0
  14. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_skills_sync.py +0 -0
  15. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_sync_config.py +0 -0
  16. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/setup.cfg +0 -0
  17. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/setup.py +0 -0
  18. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_rules_sync.py +0 -0
  19. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_rules_sync_integration.py +0 -0
  20. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_skills_sync.py +0 -0
  21. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_migration.py +0 -0
  22. {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_windows_daemon.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-rules-sync
3
- Version: 1.4.3
3
+ Version: 1.4.4
4
4
  Summary: Synchronize rules and skills across AI coding assistants (Claude Code, Cursor, Gemini, OpenCode, Codex)
5
5
  Author: Agent Rules Sync Contributors
6
6
  License: MIT
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Guardian - macOS watchdog for agent-rules-sync.
4
+ Monitors the daemon and shows a popup if it's down or erroring.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import subprocess
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+
14
+ class AgentGuardian:
15
+ def __init__(self):
16
+ self.config_dir = Path.home() / ".config" / "agent-rules-sync"
17
+ self.pid_file = self.config_dir / "daemon.pid"
18
+ self.log_file = self.config_dir / "daemon.log"
19
+ self.last_error_count = self._get_error_count()
20
+ self.last_popup_time = 0
21
+ self.popup_cooldown = 300 # 5 minutes
22
+
23
+ def _get_error_count(self):
24
+ if not self.log_file.exists():
25
+ return 0
26
+ try:
27
+ content = self.log_file.read_text()
28
+ return content.count("ERROR:")
29
+ except Exception:
30
+ return 0
31
+
32
+ def is_daemon_running(self):
33
+ if not self.pid_file.exists():
34
+ return False
35
+ try:
36
+ pid = int(self.pid_file.read_text().strip())
37
+ # Check if process exists
38
+ os.kill(pid, 0)
39
+ return True
40
+ except (OSError, ValueError):
41
+ return False
42
+
43
+ def show_popup(self, message):
44
+ now = time.time()
45
+ if now - self.last_popup_time < self.popup_cooldown:
46
+ return
47
+
48
+ self.last_popup_time = now
49
+
50
+ applescript = f'''
51
+ set result to display dialog "{message}" with title "Agent Sync Guardian" buttons {{"Inspect", "Restart", "Dismiss"}} default button "Restart" with icon caution
52
+ return button returned of result
53
+ '''
54
+
55
+ try:
56
+ result = subprocess.check_output(['osascript', '-e', applescript]).decode('utf-8').strip()
57
+
58
+ if result == "Inspect":
59
+ subprocess.run(['open', str(self.log_file)])
60
+ elif result == "Restart":
61
+ self.restart_daemon()
62
+ except subprocess.CalledProcessError:
63
+ # User clicked cancel or closed dialog
64
+ pass
65
+
66
+ def restart_daemon(self):
67
+ # Use the agent-sync command to stop and start
68
+ try:
69
+ subprocess.run(['agent-sync', 'stop'], capture_output=True)
70
+ time.sleep(1)
71
+ subprocess.run(['agent-sync'], capture_output=True)
72
+ # Notify success
73
+ subprocess.run(['osascript', '-e', 'display notification "Agent Sync Daemon restarted successfully." with title "Agent Sync Guardian"'])
74
+ except Exception as e:
75
+ subprocess.run(['osascript', '-e', f'display notification "Failed to restart daemon: {e}" with title "Agent Sync Guardian"'])
76
+
77
+ def run(self):
78
+ print(f"Guardian started at {datetime.now()}")
79
+ while True:
80
+ time.sleep(10) # Check every 10 seconds
81
+
82
+ # Check if running
83
+ if not self.is_daemon_running():
84
+ self.show_popup("Agent Sync Daemon is NOT running.")
85
+ continue
86
+
87
+ # Check for new errors
88
+ current_errors = self._get_error_count()
89
+ if current_errors > self.last_error_count:
90
+ new_errors = current_errors - self.last_error_count
91
+ self.last_error_count = current_errors
92
+ self.show_popup(f"Agent Sync Daemon detected {new_errors} new errors.")
93
+
94
+ if __name__ == "__main__":
95
+ if sys.platform != "darwin":
96
+ print("Guardian is only supported on macOS.")
97
+ sys.exit(1)
98
+
99
+ guardian = AgentGuardian()
100
+ guardian.run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-rules-sync
3
- Version: 1.4.3
3
+ Version: 1.4.4
4
4
  Summary: Synchronize rules and skills across AI coding assistants (Claude Code, Cursor, Gemini, OpenCode, Codex)
5
5
  Author: Agent Rules Sync Contributors
6
6
  License: MIT
@@ -1,5 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
+ agent_guardian.py
3
4
  agent_rules_sync.py
4
5
  agent_settings_sync.py
5
6
  agent_skills_sync.py
@@ -1,3 +1,4 @@
1
+ agent_guardian
1
2
  agent_rules_sync
2
3
  agent_settings_sync
3
4
  agent_skills_sync
@@ -950,7 +950,7 @@ class AgentRulesSync:
950
950
 
951
951
 
952
952
  SYNC_SCOPES = ["rules", "skills", "settings", "all"]
953
- COMMANDS = ["sync", "delete-skill", "setup", "status", "stop", "watch", "daemon"]
953
+ COMMANDS = ["sync", "delete-skill", "setup", "status", "stop", "watch", "daemon", "guardian"]
954
954
 
955
955
 
956
956
  def _run_sync(syncer, scopes):
@@ -1054,6 +1054,18 @@ Sync scope examples:
1054
1054
  elif args.command == 'stop':
1055
1055
  syncer.daemon_stop()
1056
1056
 
1057
+ elif args.command == 'guardian':
1058
+ if sys.platform != "darwin":
1059
+ print("❌ Guardian is only supported on macOS.")
1060
+ sys.exit(1)
1061
+ try:
1062
+ from agent_guardian import AgentGuardian
1063
+ guardian = AgentGuardian()
1064
+ guardian.run()
1065
+ except ImportError:
1066
+ print("❌ agent_guardian.py not found.")
1067
+ sys.exit(1)
1068
+
1057
1069
  else: # daemon (default)
1058
1070
  if syncer.pid_file.exists():
1059
1071
  try:
@@ -13,26 +13,45 @@ import subprocess
13
13
 
14
14
  def install_macos():
15
15
  """Install as macOS launchd service"""
16
+ success = _install_launchd_service(
17
+ label="com.local.agent-rules-sync",
18
+ args=["-m", "agent_rules_sync"],
19
+ description="Agent Rules Sync Daemon"
20
+ )
21
+
22
+ if success:
23
+ # Also install the Guardian watchdog
24
+ _install_launchd_service(
25
+ label="com.local.agent-rules-sync-guardian",
26
+ args=["-m", "agent_rules_sync", "guardian"],
27
+ description="Agent Rules Sync Guardian"
28
+ )
29
+ print(f"✓ macOS guardian watchdog installed and started")
30
+
31
+ return success
32
+
33
+
34
+ def _install_launchd_service(label, args, description):
35
+ """Helper to install a macOS launchd service"""
16
36
  plist_content = """<?xml version="1.0" encoding="UTF-8"?>
17
37
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18
38
  <plist version="1.0">
19
39
  <dict>
20
40
  <key>Label</key>
21
- <string>com.local.agent-rules-sync</string>
41
+ <string>{label}</string>
22
42
  <key>ProgramArguments</key>
23
43
  <array>
24
44
  <string>{python_path}</string>
25
- <string>-m</string>
26
- <string>agent_rules_sync</string>
45
+ {args_xml}
27
46
  </array>
28
47
  <key>RunAtLoad</key>
29
48
  <true/>
30
49
  <key>KeepAlive</key>
31
50
  <true/>
32
51
  <key>StandardOutPath</key>
33
- <string>{log_dir}/stdout.log</string>
52
+ <string>{log_dir}/{label}.stdout.log</string>
34
53
  <key>StandardErrorPath</key>
35
- <string>{log_dir}/stderr.log</string>
54
+ <string>{log_dir}/{label}.stderr.log</string>
36
55
  <key>ProcessType</key>
37
56
  <string>Background</string>
38
57
  </dict>
@@ -40,33 +59,32 @@ def install_macos():
40
59
 
41
60
  python_path = sys.executable
42
61
  log_dir = str(Path.home() / ".config" / "agent-rules-sync")
43
-
44
- # Create log directory
45
62
  Path(log_dir).mkdir(parents=True, exist_ok=True)
46
63
 
47
- plist_path = Path.home() / "Library" / "LaunchAgents" / "com.local.agent-rules-sync.plist"
64
+ args_xml = "\n".join([f" <string>{arg}</string>" for arg in args])
65
+
66
+ plist_path = Path.home() / "Library" / "LaunchAgents" / f"{label}.plist"
48
67
  plist_path.parent.mkdir(parents=True, exist_ok=True)
49
68
 
50
- plist_content = plist_content.format(python_path=python_path, log_dir=log_dir)
69
+ formatted_content = plist_content.format(
70
+ label=label,
71
+ python_path=python_path,
72
+ log_dir=log_dir,
73
+ args_xml=args_xml
74
+ )
51
75
 
52
76
  with open(plist_path, 'w') as f:
53
- f.write(plist_content)
77
+ f.write(formatted_content)
54
78
 
55
- # Set permissions
56
79
  os.chmod(plist_path, 0o644)
57
80
 
58
- # Load the service
59
81
  try:
60
- subprocess.run(['launchctl', 'load', str(plist_path)], check=True,
61
- capture_output=True)
62
- print(f"✓ macOS daemon installed and started")
63
- print(f" Service: com.local.agent-rules-sync")
64
- print(f" Plist: {plist_path}")
65
- print(f" Logs: {log_dir}/stdout.log and stderr.log")
82
+ subprocess.run(['launchctl', 'unload', str(plist_path)], capture_output=True)
83
+ subprocess.run(['launchctl', 'load', str(plist_path)], check=True, capture_output=True)
84
+ print(f"✓ {description} installed")
66
85
  return True
67
86
  except subprocess.CalledProcessError as e:
68
- print(f"⚠️ Could not load launchd service: {e}")
69
- print(f" Run manually: launchctl load {plist_path}")
87
+ print(f"⚠️ Could not load {label}: {e}")
70
88
  return False
71
89
 
72
90
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agent-rules-sync"
7
- version = "1.4.3"
7
+ version = "1.4.4"
8
8
  description = "Synchronize rules and skills across AI coding assistants (Claude Code, Cursor, Gemini, OpenCode, Codex)"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -39,4 +39,4 @@ agent-sync = "agent_rules_sync:main"
39
39
  agent-rules-sync = "agent_rules_sync:main"
40
40
 
41
41
  [tool.setuptools]
42
- py-modules = ["agent_rules_sync", "agent_skills_sync", "agent_settings_sync", "agent_sync_config", "install_daemon"]
42
+ py-modules = ["agent_rules_sync", "agent_skills_sync", "agent_settings_sync", "agent_sync_config", "install_daemon", "agent_guardian"]