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/python/desktop.py CHANGED
@@ -1,39 +1,99 @@
1
- """Optional macOS menu app."""
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
- def menubar(state, launcher=None, build_only=False):
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
- binary.parent.mkdir(parents=True, exist_ok=True)
21
- if not binary.exists() or source.stat().st_mtime > binary.stat().st_mtime:
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
- "Swift compiler is unavailable; install Apple command-line tools to build the optional menu app"
76
+ "Install Apple command-line tools to build the menu app: xcode-select --install"
26
77
  )
27
- subprocess.run([compiler, str(source), "-o", str(binary)], check=True)
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": "local.relay.menu",
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
- spawn(
47
- ["/usr/bin/open", "-n", str(app), "--args", str(launcher)],
48
- stdin=subprocess.DEVNULL,
49
- stdout=subprocess.DEVNULL,
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}