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.
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/PKG-INFO +1 -1
- agent_rules_sync-1.4.4/agent_guardian.py +100 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/PKG-INFO +1 -1
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/SOURCES.txt +1 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/top_level.txt +1 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.py +13 -1
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/install_daemon.py +38 -20
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/pyproject.toml +2 -2
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/LICENSE +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/README.md +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/dependency_links.txt +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/entry_points.txt +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_settings_sync.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_skills_sync.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_sync_config.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/setup.cfg +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/setup.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_rules_sync.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_rules_sync_integration.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_skills_sync.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_migration.py +0 -0
- {agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_windows_daemon.py +0 -0
|
@@ -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()
|
|
@@ -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>
|
|
41
|
+
<string>{label}</string>
|
|
22
42
|
<key>ProgramArguments</key>
|
|
23
43
|
<array>
|
|
24
44
|
<string>{python_path}</string>
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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', '
|
|
61
|
-
|
|
62
|
-
print(f"✓
|
|
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
|
|
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.
|
|
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"]
|
|
File without changes
|
|
File without changes
|
{agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/agent_rules_sync.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_rules_sync-1.4.3 → agent_rules_sync-1.4.4}/tests/test_agent_rules_sync_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|