feat: панель как фоновая служба без iTerm2 + цвет фона на клиенте #5
39
files/launchagent.py
Normal file
39
files/launchagent.py
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Пишет описание службы launchd для сервера панели: запуск при входе, перезапуск при падении.
|
||||
|
||||
launchagent.py write <plist> <python> <claude_agents.py> <log>
|
||||
"""
|
||||
import os
|
||||
import plistlib
|
||||
import sys
|
||||
|
||||
LABEL = "com.andrey.claude-agents" # то же, что SERVICE_LABEL в claude_agents.py
|
||||
|
||||
|
||||
def plist(python, script, log):
|
||||
return {
|
||||
"Label": LABEL,
|
||||
"ProgramArguments": [python, script, "--headless"],
|
||||
"RunAtLoad": True,
|
||||
"KeepAlive": True,
|
||||
"StandardOutPath": log,
|
||||
"StandardErrorPath": log,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 6 or sys.argv[1] != "write":
|
||||
print(__doc__.strip(), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
path, python, script, log = (os.path.expanduser(a) for a in sys.argv[2:])
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(log), exist_ok=True)
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "wb") as f:
|
||||
plistlib.dump(plist(python, script, log), f, fmt=plistlib.FMT_XML)
|
||||
os.replace(tmp, path)
|
||||
print(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
49
tests/test_launchagent.py
Normal file
49
tests/test_launchagent.py
Normal file
@ -0,0 +1,49 @@
|
||||
import os
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
sys.path.insert(0, os.path.join(ROOT, "files"))
|
||||
import claude_agents # noqa: E402
|
||||
import launchagent # noqa: E402
|
||||
|
||||
SCRIPT = os.path.join(ROOT, "files", "launchagent.py")
|
||||
|
||||
|
||||
class Plist(unittest.TestCase):
|
||||
def test_label_matches_server(self):
|
||||
self.assertEqual(launchagent.LABEL, "com.andrey.claude-agents")
|
||||
self.assertEqual(launchagent.LABEL, claude_agents.SERVICE_LABEL)
|
||||
|
||||
def test_plist_shape(self):
|
||||
self.assertEqual(
|
||||
launchagent.plist("/opt/homebrew/bin/python3", "/x/claude_agents.py", "/l/panel.log"),
|
||||
{"Label": "com.andrey.claude-agents",
|
||||
"ProgramArguments": ["/opt/homebrew/bin/python3", "/x/claude_agents.py", "--headless"],
|
||||
"RunAtLoad": True, "KeepAlive": True,
|
||||
"StandardOutPath": "/l/panel.log", "StandardErrorPath": "/l/panel.log"})
|
||||
|
||||
|
||||
class Cli(unittest.TestCase):
|
||||
def test_cli_writes_plist_with_spaces(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
target = f"{tmp}/LaunchAgents/a.plist"
|
||||
args = ("/usr/bin/python3", f"{tmp}/Application Support/claude_agents.py",
|
||||
f"{tmp}/Logs/claude-agents/panel.log")
|
||||
r = subprocess.run([sys.executable, SCRIPT, "write", target, *args],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
with open(target, "rb") as f:
|
||||
self.assertEqual(plistlib.load(f), launchagent.plist(*args))
|
||||
self.assertTrue(os.path.isdir(f"{tmp}/Logs/claude-agents"))
|
||||
|
||||
def test_cli_bad_args(self):
|
||||
r = subprocess.run([sys.executable, SCRIPT, "write"], capture_output=True, text=True)
|
||||
self.assertEqual(r.returncode, 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
x
Reference in New Issue
Block a user