cdx-manager 0.18.4 → 0.18.5
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 +37 -6
- package/VERSION +1 -0
- package/changelogs/CHANGELOGS_0_18_5.md +147 -0
- package/checksums/release-archives.json +11 -0
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/agent_notify.py +192 -12
- package/src/cli.py +24 -9
- package/src/cli_args.py +1 -1
- package/src/commands/maintenance.py +22 -13
- package/src/commands/tray.py +245 -6
- package/src/session_service.py +23 -1
- package/src/tray_contract.py +11 -2
- package/src/tray_defaults.py +48 -0
- package/src/tray_events.py +9 -1
- package/src/tray_install.py +101 -8
- package/src/tray_logics.py +141 -0
- package/src/tray_plugin_actions.py +62 -0
- package/src/tray_plugins.py +163 -0
- package/src/tray_restart.py +152 -0
- package/src/tray_terminal.py +80 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Which terminal a tray session row opens.
|
|
2
|
+
|
|
3
|
+
The companion picks one per platform today: Terminal.app on macOS, the first of
|
|
4
|
+
four names that answers on Linux. Someone who works in iTerm or Ghostty gets a
|
|
5
|
+
second terminal application opened beside the one they were already in, which is
|
|
6
|
+
the opposite of the shortcut the row is meant to be.
|
|
7
|
+
|
|
8
|
+
What is stored is an application, never a command, and that restriction is the
|
|
9
|
+
whole security argument. A preference that could hold a command line would be a
|
|
10
|
+
way to run anything, in a process the user did not start, triggered by clicking
|
|
11
|
+
a menu row — and it would be writable by anything that can write CDX's state.
|
|
12
|
+
The validation below is what makes "open my terminal" impossible to turn into
|
|
13
|
+
"run this". Nothing here reaches a shell: the value names an application and the
|
|
14
|
+
platform's own launcher resolves it.
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
STATE_VERSION = 1
|
|
21
|
+
# An application name or a bundle identifier, and nothing that could be read as
|
|
22
|
+
# a command. No slashes, so it cannot name a path; no quotes, semicolons,
|
|
23
|
+
# ampersands, pipes, backticks or dollars, so it cannot carry a shell fragment;
|
|
24
|
+
# it must start with a letter or a digit, so it cannot be read as a flag.
|
|
25
|
+
_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$")
|
|
26
|
+
|
|
27
|
+
REFUSAL = (
|
|
28
|
+
"A terminal preference is an application name, not a command: "
|
|
29
|
+
"letters, digits, spaces, dots, dashes and underscores only. "
|
|
30
|
+
'Examples: "iTerm", "Ghostty", "WezTerm", "kitty", "wt".'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def state_path(base_dir):
|
|
35
|
+
return os.path.join(base_dir, "tray", "terminal.json")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def terminal_preference(base_dir):
|
|
39
|
+
"""The chosen terminal, or None for the platform's default.
|
|
40
|
+
|
|
41
|
+
Anything unreadable, unrecognised, or no longer valid reads as no
|
|
42
|
+
preference. A value that fails validation today must not be honoured just
|
|
43
|
+
because it was written before the rule existed.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
with open(state_path(base_dir), encoding="utf-8") as handle:
|
|
47
|
+
state = json.load(handle)
|
|
48
|
+
except (OSError, ValueError):
|
|
49
|
+
return None
|
|
50
|
+
if not isinstance(state, dict) or state.get("version") != STATE_VERSION:
|
|
51
|
+
return None
|
|
52
|
+
name = state.get("terminal")
|
|
53
|
+
return name if isinstance(name, str) and _NAME.match(name) else None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def valid_terminal(name):
|
|
57
|
+
return isinstance(name, str) and bool(_NAME.match(name))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def set_terminal(base_dir, name):
|
|
61
|
+
"""Record a preferred terminal. Raises ValueError on anything else."""
|
|
62
|
+
if not valid_terminal(name):
|
|
63
|
+
raise ValueError(REFUSAL)
|
|
64
|
+
_write(base_dir, {"version": STATE_VERSION, "terminal": name})
|
|
65
|
+
return name
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def clear_terminal(base_dir):
|
|
69
|
+
"""Return to the platform's default."""
|
|
70
|
+
_write(base_dir, {"version": STATE_VERSION, "terminal": None})
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _write(base_dir, state):
|
|
75
|
+
path = state_path(base_dir)
|
|
76
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
77
|
+
temp = f"{path}.{os.getpid()}.tmp"
|
|
78
|
+
with open(temp, "w", encoding="utf-8") as handle:
|
|
79
|
+
json.dump(state, handle, indent=2)
|
|
80
|
+
os.replace(temp, path)
|