tray4hermes 2.0.17__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.
- tray4hermes-2.0.17/LICENSE +21 -0
- tray4hermes-2.0.17/PKG-INFO +624 -0
- tray4hermes-2.0.17/README.md +592 -0
- tray4hermes-2.0.17/pyproject.toml +149 -0
- tray4hermes-2.0.17/setup.cfg +4 -0
- tray4hermes-2.0.17/src/tray4hermes/__init__.py +17 -0
- tray4hermes-2.0.17/src/tray4hermes/__main__.py +176 -0
- tray4hermes-2.0.17/src/tray4hermes/_locales/cs/LC_MESSAGES/tray4hermes.mo +0 -0
- tray4hermes-2.0.17/src/tray4hermes/app.py +506 -0
- tray4hermes-2.0.17/src/tray4hermes/data/tray4hermes.desktop +9 -0
- tray4hermes-2.0.17/src/tray4hermes/i18n.py +311 -0
- tray4hermes-2.0.17/src/tray4hermes/icons.py +97 -0
- tray4hermes-2.0.17/src/tray4hermes/lock.py +54 -0
- tray4hermes-2.0.17/src/tray4hermes/log_dialog.py +787 -0
- tray4hermes-2.0.17/src/tray4hermes/log_parse.py +106 -0
- tray4hermes-2.0.17/src/tray4hermes/log_settings.py +343 -0
- tray4hermes-2.0.17/src/tray4hermes/log_theme.py +100 -0
- tray4hermes-2.0.17/src/tray4hermes/paths.py +75 -0
- tray4hermes-2.0.17/src/tray4hermes/py.typed +0 -0
- tray4hermes-2.0.17/src/tray4hermes/state.py +264 -0
- tray4hermes-2.0.17/src/tray4hermes/tray_settings.py +291 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/PKG-INFO +624 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/SOURCES.txt +45 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/dependency_links.txt +1 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/entry_points.txt +2 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/requires.txt +7 -0
- tray4hermes-2.0.17/src/tray4hermes.egg-info/top_level.txt +1 -0
- tray4hermes-2.0.17/tests/test_app.py +624 -0
- tray4hermes-2.0.17/tests/test_cli.py +74 -0
- tray4hermes-2.0.17/tests/test_gateway_control.py +154 -0
- tray4hermes-2.0.17/tests/test_i18n_build.py +196 -0
- tray4hermes-2.0.17/tests/test_i18n_parity.py +177 -0
- tray4hermes-2.0.17/tests/test_i18n_runtime.py +289 -0
- tray4hermes-2.0.17/tests/test_launchers.py +251 -0
- tray4hermes-2.0.17/tests/test_lock.py +57 -0
- tray4hermes-2.0.17/tests/test_log_parse.py +91 -0
- tray4hermes-2.0.17/tests/test_log_refresh.py +85 -0
- tray4hermes-2.0.17/tests/test_log_settings.py +155 -0
- tray4hermes-2.0.17/tests/test_log_shortcuts.py +189 -0
- tray4hermes-2.0.17/tests/test_log_toolbar.py +100 -0
- tray4hermes-2.0.17/tests/test_readme_freshness.py +104 -0
- tray4hermes-2.0.17/tests/test_readme_parity.py +143 -0
- tray4hermes-2.0.17/tests/test_settings_dialog.py +75 -0
- tray4hermes-2.0.17/tests/test_state.py +315 -0
- tray4hermes-2.0.17/tests/test_test_environment.py +24 -0
- tray4hermes-2.0.17/tests/test_tray_settings.py +198 -0
- tray4hermes-2.0.17/tests/test_versioning.py +232 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MoDDO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tray4hermes
|
|
3
|
+
Version: 2.0.17
|
|
4
|
+
Summary: KDE/Plasma tray monitor and controller for Hermes Gateway (Nous Research Hermes Agent)
|
|
5
|
+
Author: MoDDO
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MoDD0/tray4hermes
|
|
8
|
+
Project-URL: Repository, https://github.com/MoDD0/tray4hermes.git
|
|
9
|
+
Project-URL: Issues, https://github.com/MoDD0/tray4hermes/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/MoDD0/tray4hermes/blob/main/README.md#roadmap-next-up-ideas
|
|
11
|
+
Project-URL: Mirror, https://forgejo.he1.co/HERMbuddy/tray4hermes
|
|
12
|
+
Keywords: hermes,hermes-agent,tray,kde,plasma,system-tray,qt5,pyqt5
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: X11 Applications
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: PyQt5==5.15.11
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest==9.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-qt==4.5.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff==0.14.0; extra == "dev"
|
|
30
|
+
Requires-Dist: bandit==1.8.6; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# tray4hermes
|
|
34
|
+
|
|
35
|
+
<!-- tray4hermes:version -->
|
|
36
|
+
[](https://github.com/MoDD0/tray4hermes)
|
|
37
|
+
[](https://github.com/MoDD0/tray4hermes/actions/workflows/ci.yml)
|
|
38
|
+
[](https://opensource.org/licenses/MIT)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
[](https://github.com/astral-sh/ruff)
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
*A real KDE Plasma 5 tray — the tray4hermes icon slots in next to your
|
|
45
|
+
other SNI apps. Screenshot from a Manjaro KDE session where this tray
|
|
46
|
+
is live.*
|
|
47
|
+
|
|
48
|
+
<!-- i18n:available-languages:START -->
|
|
49
|
+
<!-- DO NOT EDIT — auto-generated by scripts/i18n_build.py from the files in docs/i18n/. -->
|
|
50
|
+
<!-- Available languages: en → English, cs → Čeština -->
|
|
51
|
+
<!-- i18n:available-languages:END -->
|
|
52
|
+
|
|
53
|
+
> **Canonical:** English (this file)
|
|
54
|
+
>
|
|
55
|
+
> **Other languages:** [Čeština](docs/README.cs.md)
|
|
56
|
+
|
|
57
|
+
A KDE/Plasma system-tray monitor and controller for **Hermes Gateway**, the
|
|
58
|
+
messaging service included with
|
|
59
|
+
[Hermes Agent](https://github.com/NousResearch/hermes-agent) by Nous Research.
|
|
60
|
+
|
|
61
|
+
> tray4hermes is **read-only with respect to Hermes Agent**. It controls
|
|
62
|
+
> the gateway via `systemctl --user`, persists one small JSON file of its
|
|
63
|
+
> own, and reads everything else. It does not store tokens, does not
|
|
64
|
+
> configure providers, does not edit `~/.hermes/config.yaml`. All of
|
|
65
|
+
> that lives in Hermes Agent itself.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Overview
|
|
70
|
+
|
|
71
|
+
`hermes-gateway` runs as a `systemd --user` service. tray4hermes adds a KDE
|
|
72
|
+
tray interface for monitoring and controlling it. The application:
|
|
73
|
+
|
|
74
|
+
- **shows the gateway state** directly in the KDE panel (green/orange/red)
|
|
75
|
+
- **lets you Start / Stop / Restart** without opening a terminal
|
|
76
|
+
- **switches profiles** (`default`, `work`, `off`…) from a menu
|
|
77
|
+
- **displays logs** in a viewer with coloured levels, per-level filters,
|
|
78
|
+
search, traceback awareness and a time-window filter
|
|
79
|
+
- **speaks English and Czech**, switchable at runtime without a restart
|
|
80
|
+
- **reads Hermes runtime state** and uses the standard `systemctl` API.
|
|
81
|
+
|
|
82
|
+
> ⚠️ **Disclaimer:** tray4hermes is a *passive convenience addon*, not an
|
|
83
|
+
> official Hermes Agent component. Hermes Agent runs perfectly well
|
|
84
|
+
> without it. Use it if you like it; ignore it if you don't.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Features
|
|
89
|
+
|
|
90
|
+
- 📊 **Live status icon** in the system tray (🟢 active, 🟠 warming,
|
|
91
|
+
🔵 activating, ⚫ inactive, 🔴 failed, ⚪ unknown)
|
|
92
|
+
- ▶️ **Start / Stop / Restart** of `hermes-gateway.service` in a single click
|
|
93
|
+
- 🔄 **Profile switcher** submenu, driven by `~/.hermes/profiles/`
|
|
94
|
+
- 📋 **Log viewer** — *see below*, it's quite polished
|
|
95
|
+
- ⚙️ **Open Hermes config** in your default editor
|
|
96
|
+
- 💻 **Launch Hermes CLI** in a new terminal
|
|
97
|
+
- 🛠️ **Settings dialog** — UI language plus the defaults the log viewer
|
|
98
|
+
starts with, stored alongside the rest of the tray state
|
|
99
|
+
- 🌍 **English and Czech UI** — pick a language in the Settings dialog and
|
|
100
|
+
the tray re-labels itself immediately, no restart; `tray4hermes -L en`
|
|
101
|
+
(or `cs`) overrides the stored choice for a single run, and
|
|
102
|
+
`tray4hermes --language` lists what the installed build ships
|
|
103
|
+
|
|
104
|
+
### Log viewer
|
|
105
|
+
|
|
106
|
+
The log viewer reads `~/.hermes/logs/gateway.log` and provides:
|
|
107
|
+
|
|
108
|
+
- **Coloured log levels**: `DEBUG` gray, `INFO` white, `WARNING` yellow,
|
|
109
|
+
`ERROR` red, `CRITICAL` red + full-row highlight
|
|
110
|
+
- **Line-number gutter** like Qt Creator / VS Code
|
|
111
|
+
- **Per-level filters** on a toolbar row of their own, so they and the
|
|
112
|
+
controls above them stay visible instead of collapsing into the `»`
|
|
113
|
+
overflow popup — show only what you want
|
|
114
|
+
- **TRACEBACK toggle** — a separate category for stack traces; turn
|
|
115
|
+
it off to see only messages, or on to keep both
|
|
116
|
+
- **Time-window filter** (All / 5m / 15m / 1h / 6h / 24h) — show only
|
|
117
|
+
logs from the last hour, etc.
|
|
118
|
+
- **Reverse order** — flip to `journalctl` style (newest at top); every new
|
|
119
|
+
entry appears first
|
|
120
|
+
- **Max lines** spinbox — rolling buffer (0 = unlimited). Steps by 1; type any value manually
|
|
121
|
+
- **Search** in a collapsible find bar under the log (`Ctrl+F` opens and
|
|
122
|
+
focuses it, `F3` next, `Shift+F3` prev, `Esc` closes). It deliberately sits
|
|
123
|
+
outside the toolbar, so it stays reachable at any window width
|
|
124
|
+
- **Auto-scroll toggle** (default ON; OFF = preserve position on refresh).
|
|
125
|
+
While investigating, the viewer preserves a manually selected scroll
|
|
126
|
+
position even when auto-scroll is ON; it only follows the live edge when
|
|
127
|
+
you are already viewing that edge
|
|
128
|
+
- **Word-wrap toggle**
|
|
129
|
+
- **Copy / Clear / Refresh** actions
|
|
130
|
+
- **Settings dialog** (font size, max lines, per-level visibility, …)
|
|
131
|
+
- **Remembers window size/position** between opens
|
|
132
|
+
- **Persisted** — all settings are saved to
|
|
133
|
+
`~/.config/tray4hermes/state.json`
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Architecture
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
┌──────────────────────────────────────────────────────┐
|
|
141
|
+
│ Hermes Agent (Nous Research) │
|
|
142
|
+
│ • hermes-gateway.service (systemd --user) │
|
|
143
|
+
│ • Hermes Desktop (Electron consumer) │
|
|
144
|
+
│ • CLI / TUI / MCP / Plugins │
|
|
145
|
+
└──────────────────────┬───────────────────────────────┘
|
|
146
|
+
│ shares
|
|
147
|
+
▼
|
|
148
|
+
~/.hermes/ ← single source of truth
|
|
149
|
+
├── config.yaml
|
|
150
|
+
├── auth.json
|
|
151
|
+
├── gateway_state.json
|
|
152
|
+
├── logs/gateway.log
|
|
153
|
+
└── profiles/<name>/
|
|
154
|
+
▲
|
|
155
|
+
│ reads (read-only)
|
|
156
|
+
┌──────────────────────┴───────────────────────────────┐
|
|
157
|
+
│ tray4hermes (this package) │
|
|
158
|
+
│ • systray icon • Start/Stop/Restart │
|
|
159
|
+
│ • profile switcher • log viewer │
|
|
160
|
+
│ • writes only: ~/.config/tray4hermes/state.json │
|
|
161
|
+
└──────────────────────────────────────────────────────┘
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The package has zero coupling to the Hermes Agent source code. It only
|
|
165
|
+
knows about files in `~/.hermes/`, the systemd unit name, and the path
|
|
166
|
+
to the `hermes` CLI. The tray can be uninstalled at any time without
|
|
167
|
+
affecting the gateway.
|
|
168
|
+
|
|
169
|
+
## State machine
|
|
170
|
+
|
|
171
|
+
The tray combines two sources of truth into six discrete states:
|
|
172
|
+
|
|
173
|
+
| Code | Icon | Meaning |
|
|
174
|
+
|------|------|---------|
|
|
175
|
+
| `active` | 🟢 | Gateway running, at least one platform connected |
|
|
176
|
+
| `warming` | 🟠 | Gateway running, credentials/platforms still initialising |
|
|
177
|
+
| `activating` | 🔵 | systemd is starting the service |
|
|
178
|
+
| `inactive` | ⚫ | Gateway stopped |
|
|
179
|
+
| `failed` | 🔴 | systemd unit failed |
|
|
180
|
+
| `unknown` | ⚪ | Cannot determine state (both sources unavailable) |
|
|
181
|
+
|
|
182
|
+
`gateway_state.json` is the primary source when fresh (< 1 hour old);
|
|
183
|
+
`systemctl is-active` is the fallback. The two-source design avoids
|
|
184
|
+
the OAuth warm-up race where the systemd unit shows `active` for a few
|
|
185
|
+
seconds before the first model call actually succeeds.
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- **Linux** (developed primarily on **Manjaro KDE**; should work on
|
|
190
|
+
any distro with KDE Plasma 5, see [Platform support](#platform-support))
|
|
191
|
+
- **KDE Plasma 5** (Plasma 6 uses Qt6 — tray4hermes is Qt5; Qt6 port
|
|
192
|
+
is in the Roadmap)
|
|
193
|
+
- Python ≥ 3.11
|
|
194
|
+
- A running `hermes-gateway.service` under `systemd --user`
|
|
195
|
+
- `loginctl enable-linger $USER` for autostart after logout
|
|
196
|
+
|
|
197
|
+
## Installation
|
|
198
|
+
|
|
199
|
+
### End-user (system-wide)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
uv pip install --system tray4hermes
|
|
203
|
+
# or with pipx:
|
|
204
|
+
pipx install tray4hermes
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Then enable autostart:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Find where the .desktop file ended up after pip install
|
|
211
|
+
DESKTOP_FILE=$(python3 -c "import tray4hermes, os; \
|
|
212
|
+
print(os.path.join(os.path.dirname(tray4hermes.__file__), 'data', 'tray4hermes.desktop'))")
|
|
213
|
+
cp "$DESKTOP_FILE" ~/.config/autostart/tray4hermes.desktop
|
|
214
|
+
# Adjust the Exec= line to point at your installed tray4hermes script
|
|
215
|
+
# (the wheel ships it at <prefix>/bin/tray4hermes).
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Development (editable)
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git clone https://github.com/MoDD0/tray4hermes.git
|
|
222
|
+
cd tray4hermes
|
|
223
|
+
uv pip install --system -e ".[dev]"
|
|
224
|
+
./scripts/dev.sh # installs deps + runs tests
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Launch the tray:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Either via the installed console script:
|
|
231
|
+
tray4hermes
|
|
232
|
+
|
|
233
|
+
# Or via the watchdog wrapper (auto-restart on crash):
|
|
234
|
+
./run.sh
|
|
235
|
+
|
|
236
|
+
# Or as a module:
|
|
237
|
+
python -m tray4hermes
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Platform support
|
|
243
|
+
|
|
244
|
+
**Primarily developed and tested on Manjaro KDE** (rolling release,
|
|
245
|
+
Plasma 5, `xcb` X11 backend).
|
|
246
|
+
|
|
247
|
+
**Should work natively on any Linux that has:**
|
|
248
|
+
|
|
249
|
+
1. **Qt5 with PyQt5** (`python -c "from PyQt5.QtWidgets import QSystemTrayIcon; print('OK')"`
|
|
250
|
+
should succeed). PySide2 is not supported — every module imports PyQt5
|
|
251
|
+
directly.
|
|
252
|
+
2. **DBus session bus** (`echo $DBUS_SESSION_BUS_ADDRESS` should be
|
|
253
|
+
set — typically automatic in a desktop session)
|
|
254
|
+
3. **A system tray implementation** respecting the freedesktop.org
|
|
255
|
+
SNI spec (KDE Plasma, Xfce, LXQt, Cinnamon, MATE, GNOME with
|
|
256
|
+
extension, Elementary OS with extension…)
|
|
257
|
+
4. **systemd --user** (or something compatible — OpenRC / runit
|
|
258
|
+
alternatives have a slightly different `hermes-gateway` API;
|
|
259
|
+
an adapter in `paths.py` would be needed)
|
|
260
|
+
|
|
261
|
+
### Distro status
|
|
262
|
+
|
|
263
|
+
Only one row here is actually tested. The rest is reasoning from how
|
|
264
|
+
close the distro is to that one — useful, but not a promise.
|
|
265
|
+
|
|
266
|
+
| Distro / DE | Status | Notes |
|
|
267
|
+
|-------------|--------|-------|
|
|
268
|
+
| **Manjaro KDE** | ✅ tested | the platform this is developed and tested on |
|
|
269
|
+
| Arch Linux + KDE | 🟡 expected to work | practically identical to Manjaro, just package from `extra` not AUR |
|
|
270
|
+
| CachyOS KDE | 🟡 expected to work | Arch-based, practically identical |
|
|
271
|
+
| **Ubuntu + KDE** | 🟡 expected to work | see below |
|
|
272
|
+
| Fedora + KDE | 🟡 expected to work | `dnf install python3-pyqt5` |
|
|
273
|
+
| openSUSE + KDE | 🟡 expected to work | `zypper install python3-PyQt5` |
|
|
274
|
+
|
|
275
|
+
### Ubuntu with Unity / GNOME / others
|
|
276
|
+
|
|
277
|
+
This is a **tray application**, so the main difference is tray support:
|
|
278
|
+
|
|
279
|
+
- **Ubuntu + KDE Plasma** (`apt install kubuntu-desktop`) — should work
|
|
280
|
+
out-of-the-box, you may need to install `python3-pyqt5` via `apt`
|
|
281
|
+
or `pip install PyQt5` for yourself.
|
|
282
|
+
- **Ubuntu + Unity (22.04+)** — Unity uses its own indicator tray,
|
|
283
|
+
not SNI. We would need to adapt `icons.py` and `app.py` so they
|
|
284
|
+
register via DBus at `com.canonical.Unity.LauncherEntry`. Technically
|
|
285
|
+
possible, but not implemented today. If you run Unity and want this
|
|
286
|
+
— open an issue.
|
|
287
|
+
- **Ubuntu + GNOME 41+** — GNOME intentionally **does not support
|
|
288
|
+
tray** without an SNI extension. You have to install an extension
|
|
289
|
+
like [AppIndicator Support](https://extensions.gnome.org/extension/615/appindicator-support/),
|
|
290
|
+
then it should work.
|
|
291
|
+
- **Ubuntu + Cinnamon / MATE / XFCE** — all support SNI tray, should
|
|
292
|
+
work out-of-the-box.
|
|
293
|
+
|
|
294
|
+
### Practical recommendation for Ubuntu users
|
|
295
|
+
|
|
296
|
+
Short version: **install KDE Plasma** and it will work with the
|
|
297
|
+
highest probability. Not the smallest package, but the cleanest path.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# 1. Install KDE Plasma desktop (meta-package, ~600 MB)
|
|
301
|
+
sudo apt install kde-plasma-desktop
|
|
302
|
+
|
|
303
|
+
# 2. Log out. On the login screen, choose the "Plasma" session, log in.
|
|
304
|
+
|
|
305
|
+
# 3. In a terminal inside the KDE session:
|
|
306
|
+
sudo apt install python3-pyqt5 # system PyQt5
|
|
307
|
+
python3 -m pip install --user tray4hermes # or from GitHub
|
|
308
|
+
python3 -m tray4hermes # start the tray
|
|
309
|
+
|
|
310
|
+
# 4. For autostart after login:
|
|
311
|
+
# The .desktop file ships inside the installed wheel at:
|
|
312
|
+
# /usr/local/lib/python3.*/site-packages/tray4hermes/data/tray4hermes.desktop
|
|
313
|
+
# (resolve with `python3 -c "import tray4hermes; import os; \
|
|
314
|
+
# print(os.path.join(os.path.dirname(tray4hermes.__file__), 'data', 'tray4hermes.desktop'))"`)
|
|
315
|
+
# Copy it to ~/.config/autostart/ and edit the Exec= path to point at
|
|
316
|
+
# your installed tray4hermes.
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why we recommend this:**
|
|
320
|
+
|
|
321
|
+
1. **Hermes Agent** runs on Ubuntu exactly the same as on Manjaro
|
|
322
|
+
(both are Linux, both have systemd --user, both have Python 3.11+).
|
|
323
|
+
*No* inherent incompatibility with Ubuntu.
|
|
324
|
+
2. **KDE Plasma's tray** implements the SNI spec most thoroughly of
|
|
325
|
+
any DE — no workaround, no extension, no "maybe works".
|
|
326
|
+
3. **Higher chance of first-try success.** Instead of 75–95% with GNOME
|
|
327
|
+
/ Cinnamon + workaround, you get ~99%.
|
|
328
|
+
|
|
329
|
+
**Alternative for purists:** if you insist on GNOME and really want
|
|
330
|
+
this to work there, follow the instructions in the `Ubuntu + GNOME 41+`
|
|
331
|
+
section above. But it's not a trivial first experience.
|
|
332
|
+
|
|
333
|
+
### Future testing
|
|
334
|
+
|
|
335
|
+
**A dedicated Ubuntu VM**: if the community brings real demand for
|
|
336
|
+
Ubuntu support, we'd happily spin up a simple **Ubuntu LTS VM**
|
|
337
|
+
(VirtualBox or LXC) with KDE Plasma in CI and run a smoke test —
|
|
338
|
+
`tray4hermes` starts → menu icon appears → click Start/Stop → verify
|
|
339
|
+
that `systemctl --user` responds. Cost would be ~1–2 days of setup +
|
|
340
|
+
scripts. We don't have it right now, because demand is currently
|
|
341
|
+
**zero** (Manjaro/KDE covers ~95% of users who have written to us).
|
|
342
|
+
If you'd like it, add it to the Roadmap. Open an issue with a `+1`
|
|
343
|
+
and a vote, or just show up to a contributing call. 🙂
|
|
344
|
+
|
|
345
|
+
### Troubleshooting
|
|
346
|
+
|
|
347
|
+
If the tray **doesn't show** on another distro:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# 1. Verify Qt5 sees a system tray
|
|
351
|
+
python -c "from PyQt5.QtWidgets import QSystemTrayIcon, QApplication; \
|
|
352
|
+
app = QApplication([]); print('available:', QSystemTrayIcon.isSystemTrayAvailable())"
|
|
353
|
+
|
|
354
|
+
# 2. Check whether DBus is running
|
|
355
|
+
echo "DBus session bus: $DBUS_SESSION_BUS_ADDRESS"
|
|
356
|
+
|
|
357
|
+
# 3. Try explicit XDG, and run in the foreground so errors reach the terminal
|
|
358
|
+
export XDG_CURRENT_DESKTOP=KDE
|
|
359
|
+
export XDG_SESSION_TYPE=x11
|
|
360
|
+
python -m tray4hermes
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
There is no `--debug` flag: the tray writes its failures to stderr, so
|
|
364
|
+
running it from a terminal as above is the whole debug story. `tray4hermes
|
|
365
|
+
--version` and `tray4hermes --language` are the only other arguments.
|
|
366
|
+
|
|
367
|
+
If `QSystemTrayIcon.isSystemTrayAvailable()` returns `False`, it's a
|
|
368
|
+
**distro/desktop combo problem**, not a tray4hermes bug. You can:
|
|
369
|
+
- open an issue with the output of `python -c "import sys; print(sys.platform, …)"`
|
|
370
|
+
plus your Qt version (`PyQt5.QtCore.PYQT_VERSION_STR`)
|
|
371
|
+
- ask in the Hermes community (or directly with us in issues, we'll
|
|
372
|
+
help extend `paths.py` for your setup)
|
|
373
|
+
|
|
374
|
+
We want it to work **everywhere** Hermes Agent has a chance of running.
|
|
375
|
+
So distro-specific PRs are very welcome.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Security
|
|
380
|
+
|
|
381
|
+
This package **does not handle any credentials, tokens, or secrets**.
|
|
382
|
+
The only file it writes is `~/.config/tray4hermes/state.json`, which
|
|
383
|
+
contains the currently-selected profile name, log-viewer preferences,
|
|
384
|
+
and a schema version:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"version": 1,
|
|
389
|
+
"selected_profile": "default",
|
|
390
|
+
"log_settings": {
|
|
391
|
+
"max_lines": 2000,
|
|
392
|
+
"auto_scroll": true,
|
|
393
|
+
"word_wrap": false,
|
|
394
|
+
"font_size": 9,
|
|
395
|
+
"show_levels": ["ERROR", "WARNING", "INFO", "DEBUG", "CRITICAL", "TRACE"],
|
|
396
|
+
"show_tracebacks": true,
|
|
397
|
+
"time_window_minutes": 0,
|
|
398
|
+
"reverse_order": false
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
If you find a security issue, please open a **private** issue via
|
|
404
|
+
GitHub's "Report a vulnerability" feature (Settings → Security →
|
|
405
|
+
Report a vulnerability). Do **not** disclose vulnerabilities in
|
|
406
|
+
public issues or commits.
|
|
407
|
+
|
|
408
|
+
### Threat model
|
|
409
|
+
|
|
410
|
+
| Vector | Mitigation |
|
|
411
|
+
|--------|------------|
|
|
412
|
+
| RCE via malicious `gateway_state.json` | Loaded as JSON only; no `eval`/`exec`/shell. Strict shape. |
|
|
413
|
+
| Log injection | Log viewer is read-only; uses `QPlainTextEdit` (escapes HTML). |
|
|
414
|
+
| Profile path injection | Profile name validated by Hermes Agent itself (`hermes profile use` returns non-zero on missing). |
|
|
415
|
+
| Lock file race | `O_CREAT\|O_EXCL` + PID liveness probe + recursive single retry. |
|
|
416
|
+
| Filesystem exhaustion on `state.json` write | Atomic `tmp` + `os.replace`; directory created with `parents=True`. |
|
|
417
|
+
|
|
418
|
+
The tray is sandboxed against the rest of Hermes Agent: even a
|
|
419
|
+
zero-day in tray4hermes cannot read `auth.json`, the `.env` file, or
|
|
420
|
+
trigger a model call. The worst it can do is crash and be restarted
|
|
421
|
+
by the watchdog — at which point it just reads the (still intact)
|
|
422
|
+
state and re-renders the tray icon.
|
|
423
|
+
|
|
424
|
+
## Development
|
|
425
|
+
|
|
426
|
+
### Run tests
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
./scripts/dev.sh # install + pytest
|
|
430
|
+
./scripts/dev.sh tests/test_state.py -v # specific file
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Tests use `QT_QPA_PLATFORM=offscreen` (set in `tests/conftest.py`) so
|
|
434
|
+
they run headless, without a display server.
|
|
435
|
+
|
|
436
|
+
### Continuous integration
|
|
437
|
+
|
|
438
|
+
[GitHub Actions](.github/workflows/ci.yml) runs on every push to `main`
|
|
439
|
+
and on every pull request: lint (`ruff check` + `ruff format --check`)
|
|
440
|
+
and the full test suite on Python 3.11, 3.12 and 3.13 — the versions
|
|
441
|
+
this package claims to support. A separate job refuses any commit that
|
|
442
|
+
lowers the package version.
|
|
443
|
+
|
|
444
|
+
### Lint & format
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
uv run ruff check src tests
|
|
448
|
+
uv run ruff format src tests
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Security scan
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
uv run bandit -c pyproject.toml -r src
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Pre-commit hooks (optional but recommended)
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
uv pip install --system pre-commit
|
|
461
|
+
pre-commit install
|
|
462
|
+
pre-commit run --all-files
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Project layout
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
tray4hermes/
|
|
469
|
+
├── pyproject.toml # PEP 621, uv-friendly, exact-pinned deps
|
|
470
|
+
├── LICENSE # MIT
|
|
471
|
+
├── README.md # generated from docs/i18n/en.md — don't edit
|
|
472
|
+
├── CONTRIBUTING.md
|
|
473
|
+
├── CLAUDE.md # briefing for AI assistants working here
|
|
474
|
+
├── .claude/ # architecture, conventions, session log, specs
|
|
475
|
+
├── .gitignore # incl. secret patterns
|
|
476
|
+
├── .pre-commit-config.yaml
|
|
477
|
+
├── run.sh # watchdog wrapper
|
|
478
|
+
├── docs/
|
|
479
|
+
│ ├── README.cs.md # generated from docs/i18n/cs.md — don't edit
|
|
480
|
+
│ ├── i18n/
|
|
481
|
+
│ │ ├── en.md # canonical README source (source of truth)
|
|
482
|
+
│ │ └── cs.md # Czech translation of en.md
|
|
483
|
+
│ └── images/
|
|
484
|
+
│ ├── preview.png # screenshot for README
|
|
485
|
+
│ └── tray4hermes.png # package logo
|
|
486
|
+
├── scripts/
|
|
487
|
+
│ ├── dev.sh # install + test convenience
|
|
488
|
+
│ ├── i18n_build.py # docs/i18n/*.md → README.md + docs/README.cs.md
|
|
489
|
+
│ ├── i18n_compile.sh # .pot extraction + .po → .mo catalogs
|
|
490
|
+
│ ├── i18n_lint.py # heading parity across README translations
|
|
491
|
+
│ └── versioning.py # SemVer bump helper
|
|
492
|
+
├── src/
|
|
493
|
+
│ └── tray4hermes/
|
|
494
|
+
│ ├── __init__.py # __version__ (single source of truth)
|
|
495
|
+
│ ├── __main__.py # python -m tray4hermes (argparse entry)
|
|
496
|
+
│ ├── app.py # HermesTray — icon, menu, poll timer
|
|
497
|
+
│ ├── state.py # @dataclass + aggregation logic
|
|
498
|
+
│ ├── paths.py # all filesystem paths and tunables
|
|
499
|
+
│ ├── icons.py # QPainter icon factory
|
|
500
|
+
│ ├── lock.py # single-instance lock
|
|
501
|
+
│ ├── log_dialog.py # LogDialog — editor, gutter, toolbars, find bar
|
|
502
|
+
│ ├── log_settings.py # viewer preferences: dataclass, persistence, dialog
|
|
503
|
+
│ ├── log_parse.py # level / timestamp / traceback parsing (no Qt widgets)
|
|
504
|
+
│ ├── log_theme.py # colours, fonts and filter constants shared by both dialogs
|
|
505
|
+
│ ├── tray_settings.py # global settings dialog (language, defaults)
|
|
506
|
+
│ ├── i18n.py # gettext wrapper + runtime language switch
|
|
507
|
+
│ ├── py.typed # PEP 561 marker
|
|
508
|
+
│ ├── _locales/ # UI catalogs, shipped inside the wheel
|
|
509
|
+
│ │ ├── tray4hermes.pot
|
|
510
|
+
│ │ └── cs/LC_MESSAGES/tray4hermes.{po,mo}
|
|
511
|
+
│ └── data/
|
|
512
|
+
│ └── tray4hermes.desktop # ships inside the wheel
|
|
513
|
+
└── tests/
|
|
514
|
+
├── conftest.py
|
|
515
|
+
├── test_app.py # Qt offscreen — tray menu and actions
|
|
516
|
+
├── test_cli.py # `python -m tray4hermes` argument handling
|
|
517
|
+
├── test_gateway_control.py # systemctl actions, profile switching
|
|
518
|
+
├── test_i18n_build.py # README build script
|
|
519
|
+
├── test_i18n_parity.py # build + lint scripts wired into pytest
|
|
520
|
+
├── test_i18n_runtime.py # gettext catalogs, msgid language
|
|
521
|
+
├── test_launchers.py # editor / terminal resolution and spawning
|
|
522
|
+
├── test_lock.py # single-instance lock
|
|
523
|
+
├── test_log_parse.py # level / timestamp parsing, both regex engines
|
|
524
|
+
├── test_log_refresh.py # what the viewer shows when the log is unreadable
|
|
525
|
+
├── test_log_settings.py # viewer settings persistence
|
|
526
|
+
├── test_log_shortcuts.py # Ctrl+F / F3 / Shift+F3 / Esc, real key path
|
|
527
|
+
├── test_log_toolbar.py # toolbar rows stay on screen at realistic widths
|
|
528
|
+
├── test_readme_freshness.py # compiled READMEs match their sources
|
|
529
|
+
├── test_readme_parity.py # image parity between en.md and cs.md
|
|
530
|
+
├── test_settings_dialog.py # log-viewer settings dialog
|
|
531
|
+
├── test_state.py # state aggregation and persistence
|
|
532
|
+
├── test_tray_settings.py # global tray settings
|
|
533
|
+
└── test_versioning.py # SemVer bump helper
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT — see [LICENSE](LICENSE).
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Hosting
|
|
543
|
+
|
|
544
|
+
**Primary host: GitHub** → https://github.com/MoDD0/tray4hermes
|
|
545
|
+
|
|
546
|
+
- All development, issues, pull requests, releases, and CI happen there.
|
|
547
|
+
- This is where contributors fork from and push branches to.
|
|
548
|
+
- Releases are tagged here first; everything else cascades from here.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Contributing
|
|
553
|
+
|
|
554
|
+
Issues, comments, suggestions — all welcome. Whether it's:
|
|
555
|
+
|
|
556
|
+
- 🐛 **Bug report** — ideally with the relevant part of
|
|
557
|
+
`~/.hermes/logs/gateway.log` and whatever `python -m tray4hermes`
|
|
558
|
+
prints when run from a terminal
|
|
559
|
+
- 💡 **Feature request** — short description of what it would be
|
|
560
|
+
for. We don't mind "wild" ideas (different backend, Wayland
|
|
561
|
+
support, custom icons…). Worth a discussion.
|
|
562
|
+
- 🎨 **UI tweak** — colours, layout, fonts, tooltips. This is
|
|
563
|
+
the area where contributor taste matters most.
|
|
564
|
+
- 📖 **Documentation** — screenshots of the log viewer are missing;
|
|
565
|
+
feel free to add them
|
|
566
|
+
- 🌍 **Localization** — the UI runs through `gettext` and ships English
|
|
567
|
+
and Czech. Another language means one `.po` file under
|
|
568
|
+
`src/tray4hermes/_locales/`; the README translations live separately in
|
|
569
|
+
`docs/i18n/`. Both routes are described in
|
|
570
|
+
[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
571
|
+
- 🐧 **Distro-specific fixes** — see [Platform support](#platform-support).
|
|
572
|
+
Every distro we test on is a contribution away
|
|
573
|
+
|
|
574
|
+
### How to send a PR / patch
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
git clone https://github.com/MoDD0/tray4hermes.git
|
|
578
|
+
cd tray4hermes
|
|
579
|
+
./scripts/dev.sh # installs deps, runs tests
|
|
580
|
+
# make your change
|
|
581
|
+
./scripts/dev.sh -v # re-run tests with verbose
|
|
582
|
+
uv run ruff check src tests
|
|
583
|
+
uv run ruff format src tests
|
|
584
|
+
git commit -m "description"
|
|
585
|
+
git push
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Rules (flexible):
|
|
589
|
+
|
|
590
|
+
1. **Don't break the tests.** The whole suite must stay green —
|
|
591
|
+
`./scripts/dev.sh` runs it.
|
|
592
|
+
2. **Don't add new runtime dependencies** without discussion — the
|
|
593
|
+
package has one runtime dependency (`PyQt5`), and we want to keep it
|
|
594
|
+
that way.
|
|
595
|
+
3. **No secrets in the diff**
|
|
596
|
+
(`grep -rE "sk-[a-z0-9]{16,}|api_key.*[a-z0-9]{20,}"`).
|
|
597
|
+
4. **No writes to `~/.hermes/*`** from inside the tray — that area is
|
|
598
|
+
owned by Hermes Agent.
|
|
599
|
+
5. **MIT-compatible contributions.** If you're adding code, stick to
|
|
600
|
+
the MIT license.
|
|
601
|
+
|
|
602
|
+
If you like this project and have an idea, **just open an issue** —
|
|
603
|
+
we'll discuss it. Even "this word doesn't sound right in the
|
|
604
|
+
translation" is welcome feedback.
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Roadmap (next-up ideas)
|
|
609
|
+
|
|
610
|
+
A few things on my mind that aren't done yet. If any of these
|
|
611
|
+
interests you more than the rest, speak up:
|
|
612
|
+
|
|
613
|
+
- **Wayland support** — currently `xcb` (X11) only because of the
|
|
614
|
+
KDE Plasma tray API; Plasma 6 + Qt6 would open Wayland. PRs welcome.
|
|
615
|
+
- **Custom icons per status** — SVG icons instead of `QPainter` raster
|
|
616
|
+
- **Log search across sessions** (FTS5 over the sessions DB)
|
|
617
|
+
- **Notifications on ERROR** — toast over D-Bus when the gateway
|
|
618
|
+
writes a traceback (I like it, but it's a matter of taste)
|
|
619
|
+
- **Settings export/import** — sharing log-viewer presets between
|
|
620
|
+
profiles
|
|
621
|
+
- **Ubuntu LTS CI VM** — see [Platform support → Future testing](#future-testing)
|
|
622
|
+
- **Plasma 6 / Qt6 port** — for the day Manjaro ships Plasma 6 by
|
|
623
|
+
default
|
|
624
|
+
|