relay-codex 0.4.1 → 0.5.1
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.
- package/README.md +26 -7
- package/docs/commands.md +123 -39
- package/docs/publishing.md +29 -13
- package/docs/troubleshooting.md +15 -3
- package/package.json +2 -2
- package/python/Menu.swift +169 -51
- package/python/commands.py +228 -22
- package/python/compatibility.py +3 -1
- package/python/desktop.py +131 -12
- package/python/execution.py +551 -0
- package/python/launch.py +375 -75
- package/python/pool.py +7 -0
- package/python/router.py +321 -74
- package/python/service.py +216 -142
- package/python/settings.py +1 -0
- package/python/tools.py +233 -0
- package/python/watch.py +74 -0
- package/tests/fake_native.py +4 -2
- package/tests/test_audit.py +345 -0
- package/tests/test_controller.py +3 -5
- package/tests/test_features.py +350 -0
- package/tests/test_launch.py +150 -0
- package/tests/test_services.py +6 -0
package/python/desktop.py
CHANGED
|
@@ -1,39 +1,99 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Native menu app and per-user login startup. No account credentials in launch files."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
4
6
|
import plistlib
|
|
5
7
|
import shutil
|
|
6
8
|
import subprocess
|
|
7
9
|
from processes import spawn
|
|
8
10
|
|
|
11
|
+
LABEL = "local.relay.menu"
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
|
|
14
|
+
def login_item(state, app=None, enabled=True, home=None):
|
|
15
|
+
home = Path(home or Path.home())
|
|
16
|
+
path = home / "Library/LaunchAgents" / (LABEL + ".plist")
|
|
17
|
+
domain = f"gui/{os.getuid()}"
|
|
18
|
+
if not enabled:
|
|
19
|
+
# Removing login startup should not close the menu currently in use.
|
|
20
|
+
path.unlink(missing_ok=True)
|
|
21
|
+
return path
|
|
22
|
+
if path.exists():
|
|
23
|
+
subprocess.run(
|
|
24
|
+
["/bin/launchctl", "bootout", domain, str(path)], capture_output=True
|
|
25
|
+
)
|
|
26
|
+
if app is None:
|
|
27
|
+
app = Path(state) / "Relay Menu.app"
|
|
28
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
payload = {
|
|
30
|
+
"Label": LABEL,
|
|
31
|
+
"ProgramArguments": [str(Path(app) / "Contents/MacOS/RelayMenu")],
|
|
32
|
+
"RunAtLoad": True,
|
|
33
|
+
"KeepAlive": {"SuccessfulExit": False},
|
|
34
|
+
"ProcessType": "Interactive",
|
|
35
|
+
"EnvironmentVariables": {
|
|
36
|
+
"PATH": os.environ.get(
|
|
37
|
+
"PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
|
|
38
|
+
),
|
|
39
|
+
"RELAY_STATE": str(Path(state).resolve()),
|
|
40
|
+
},
|
|
41
|
+
"ThrottleInterval": 30,
|
|
42
|
+
}
|
|
43
|
+
with path.open("wb") as stream:
|
|
44
|
+
plistlib.dump(payload, stream)
|
|
45
|
+
path.chmod(0o600)
|
|
46
|
+
result = subprocess.run(
|
|
47
|
+
["/bin/launchctl", "bootstrap", domain, str(path)], capture_output=True
|
|
48
|
+
)
|
|
49
|
+
if result.returncode:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"Login startup was saved, but launchctl could not load it. Run relay menubar again after signing in to macOS."
|
|
52
|
+
)
|
|
53
|
+
return path
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def menubar(state, launcher=None, build_only=False, startup=True):
|
|
11
57
|
from router import VERSION
|
|
12
58
|
|
|
13
59
|
launcher = launcher or shutil.which("relay")
|
|
14
60
|
if not launcher:
|
|
15
61
|
raise ValueError("The relay command is not on PATH")
|
|
62
|
+
launcher = str(Path(launcher).absolute())
|
|
16
63
|
source = Path(__file__).with_name("Menu.swift")
|
|
17
64
|
app = state / "Relay Menu.app"
|
|
18
65
|
contents = app / "Contents"
|
|
19
66
|
binary = contents / "MacOS" / "RelayMenu"
|
|
20
|
-
|
|
21
|
-
|
|
67
|
+
stamp = state / "menu-build.txt"
|
|
68
|
+
(contents / "relay-build.txt").unlink(missing_ok=True)
|
|
69
|
+
identity = hashlib.sha256(
|
|
70
|
+
source.read_bytes() + launcher.encode() + VERSION.encode()
|
|
71
|
+
).hexdigest()
|
|
72
|
+
if not binary.exists() or not stamp.exists() or stamp.read_text() != identity:
|
|
22
73
|
compiler = shutil.which("swiftc")
|
|
23
74
|
if not compiler:
|
|
24
75
|
raise ValueError(
|
|
25
|
-
"
|
|
76
|
+
"Install Apple command-line tools to build the menu app: xcode-select --install"
|
|
26
77
|
)
|
|
27
|
-
|
|
78
|
+
binary.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
temporary = binary.with_suffix(".new")
|
|
80
|
+
subprocess.run([compiler, str(source), "-o", str(temporary)], check=True)
|
|
81
|
+
temporary.replace(binary)
|
|
28
82
|
with (contents / "Info.plist").open("wb") as stream:
|
|
29
83
|
plistlib.dump(
|
|
30
84
|
{
|
|
31
|
-
"CFBundleIdentifier":
|
|
85
|
+
"CFBundleIdentifier": LABEL,
|
|
32
86
|
"CFBundleName": "Relay",
|
|
33
87
|
"CFBundleExecutable": "RelayMenu",
|
|
34
88
|
"CFBundleVersion": VERSION,
|
|
35
89
|
"RelayCommand": launcher,
|
|
90
|
+
"RelayState": str(state),
|
|
91
|
+
"RelayNodeDirectory": str(
|
|
92
|
+
Path(shutil.which("node") or "/usr/bin/node").parent
|
|
93
|
+
),
|
|
94
|
+
"CFBundlePackageType": "APPL",
|
|
36
95
|
"LSUIElement": True,
|
|
96
|
+
"NSHighResolutionCapable": True,
|
|
37
97
|
},
|
|
38
98
|
stream,
|
|
39
99
|
)
|
|
@@ -42,11 +102,70 @@ def menubar(state, launcher=None, build_only=False):
|
|
|
42
102
|
check=True,
|
|
43
103
|
capture_output=True,
|
|
44
104
|
)
|
|
105
|
+
stamp.write_text(identity)
|
|
45
106
|
if not build_only:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
stderr=subprocess.DEVNULL,
|
|
107
|
+
# Replace only this user's previous Relay menu, preserving all Codex tasks.
|
|
108
|
+
subprocess.run(
|
|
109
|
+
["/usr/bin/pkill", "-u", str(os.getuid()), "-x", "RelayMenu"],
|
|
110
|
+
capture_output=True,
|
|
51
111
|
)
|
|
112
|
+
if startup:
|
|
113
|
+
login_item(state, app, enabled=True)
|
|
114
|
+
else:
|
|
115
|
+
spawn(
|
|
116
|
+
["/usr/bin/open", str(app)],
|
|
117
|
+
stdin=subprocess.DEVNULL,
|
|
118
|
+
stdout=subprocess.DEVNULL,
|
|
119
|
+
stderr=subprocess.DEVNULL,
|
|
120
|
+
)
|
|
52
121
|
return app
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def job_startup(state, action, session=None, launcher=None, home=None):
|
|
125
|
+
"""Opt-in recovery of explicitly selected background jobs at Mac login."""
|
|
126
|
+
from service import resolve
|
|
127
|
+
|
|
128
|
+
home = Path(home or Path.home())
|
|
129
|
+
root = home / "Library/LaunchAgents"
|
|
130
|
+
if action == "list":
|
|
131
|
+
return [
|
|
132
|
+
p.stem.removeprefix("local.relay.job.")
|
|
133
|
+
for p in root.glob("local.relay.job.*.plist")
|
|
134
|
+
]
|
|
135
|
+
ident = resolve(state, session)
|
|
136
|
+
path = root / ("local.relay.job." + ident + ".plist")
|
|
137
|
+
domain = f"gui/{os.getuid()}"
|
|
138
|
+
if path.exists():
|
|
139
|
+
subprocess.run(
|
|
140
|
+
["/bin/launchctl", "bootout", domain, str(path)], capture_output=True
|
|
141
|
+
)
|
|
142
|
+
if action == "disable":
|
|
143
|
+
path.unlink(missing_ok=True)
|
|
144
|
+
return {"session": ident, "startup": False}
|
|
145
|
+
launcher = launcher or shutil.which("relay")
|
|
146
|
+
if not launcher:
|
|
147
|
+
raise ValueError("The relay command is not on PATH")
|
|
148
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
with path.open("wb") as stream:
|
|
150
|
+
plistlib.dump(
|
|
151
|
+
{
|
|
152
|
+
"Label": "local.relay.job." + ident,
|
|
153
|
+
"ProgramArguments": [
|
|
154
|
+
str(Path(launcher).absolute()),
|
|
155
|
+
"restart",
|
|
156
|
+
ident,
|
|
157
|
+
"--if-stopped",
|
|
158
|
+
],
|
|
159
|
+
"RunAtLoad": True,
|
|
160
|
+
"EnvironmentVariables": {
|
|
161
|
+
"PATH": os.environ.get(
|
|
162
|
+
"PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
|
|
163
|
+
),
|
|
164
|
+
"RELAY_STATE": str(Path(state).resolve()),
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
stream,
|
|
168
|
+
)
|
|
169
|
+
path.chmod(0o600)
|
|
170
|
+
# Do not run a stopped task immediately; load it at the next login.
|
|
171
|
+
return {"session": ident, "startup": True}
|