dulus-bar 0.3.0__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.
- dulus_bar-0.3.0/LICENSE +21 -0
- dulus_bar-0.3.0/PKG-INFO +212 -0
- dulus_bar-0.3.0/README.md +181 -0
- dulus_bar-0.3.0/dulus_bar/__init__.py +6 -0
- dulus_bar-0.3.0/dulus_bar/__main__.py +6 -0
- dulus_bar-0.3.0/dulus_bar/agents.py +111 -0
- dulus_bar-0.3.0/dulus_bar/assets/dulus-bird.png +0 -0
- dulus_bar-0.3.0/dulus_bar/macos_native.py +118 -0
- dulus_bar-0.3.0/dulus_bar/main.py +42 -0
- dulus_bar-0.3.0/dulus_bar/native/__init__.py +99 -0
- dulus_bar-0.3.0/dulus_bar/native/base.py +130 -0
- dulus_bar-0.3.0/dulus_bar/native/linux.py +103 -0
- dulus_bar-0.3.0/dulus_bar/native/macos.py +205 -0
- dulus_bar-0.3.0/dulus_bar/native/windows.py +97 -0
- dulus_bar-0.3.0/dulus_bar/overlay.py +1099 -0
- dulus_bar-0.3.0/dulus_bar/server.py +193 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/PKG-INFO +212 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/SOURCES.txt +24 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/dependency_links.txt +1 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/entry_points.txt +2 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/requires.txt +13 -0
- dulus_bar-0.3.0/dulus_bar.egg-info/top_level.txt +1 -0
- dulus_bar-0.3.0/pyproject.toml +55 -0
- dulus_bar-0.3.0/setup.cfg +4 -0
- dulus_bar-0.3.0/tests/test_macos_native.py +32 -0
- dulus_bar-0.3.0/tests/test_server_hub.py +91 -0
dulus_bar-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Rojo (Dulus)
|
|
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.
|
dulus_bar-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dulus-bar
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Dulus Bar — a cross-platform Dynamic Island for your AI agents (Windows / macOS / Linux)
|
|
5
|
+
Author-email: Dulus <dev@dulus.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Dulus-Ai/Dulus-bar
|
|
8
|
+
Project-URL: Repository, https://github.com/Dulus-Ai/Dulus-bar
|
|
9
|
+
Project-URL: Issues, https://github.com/Dulus-Ai/Dulus-bar/issues
|
|
10
|
+
Keywords: ai,agents,overlay,dynamic-island,notch,claude,codex,gemini,cursor,dulus,windows,macos,linux
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
23
|
+
Requires-Dist: websockets>=12.0
|
|
24
|
+
Requires-Dist: psutil>=5.9.0
|
|
25
|
+
Requires-Dist: pywin32>=306; sys_platform == "win32"
|
|
26
|
+
Requires-Dist: pyobjc-framework-Cocoa>=9.0; sys_platform == "darwin"
|
|
27
|
+
Requires-Dist: pyobjc-framework-Quartz>=9.0; sys_platform == "darwin"
|
|
28
|
+
Provides-Extra: build
|
|
29
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<h1 align="center">Dulus Bar 🦅</h1>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<strong>A cross-platform Dynamic Island for your AI agents.</strong><br>
|
|
36
|
+
Free. Native. MIT. Works with <em>any</em> agent — no account, no cloud, no lock-in.
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<code>pip install dulus-bar</code>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img alt="MIT" src="https://img.shields.io/badge/license-MIT-green">
|
|
45
|
+
<img alt="Python" src="https://img.shields.io/badge/python-3.10%2B-blue">
|
|
46
|
+
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%C2%B7%20macOS%20%C2%B7%20Linux-lightgrey">
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Dulus Bar puts a floating status island at the top of your screen that shows every
|
|
52
|
+
AI coding agent you're running — Claude Code, Codex, Gemini, Cursor, Dulus, and
|
|
53
|
+
any other CLI — and lets you **Allow / Deny** their permission prompts without
|
|
54
|
+
diving back into a terminal.
|
|
55
|
+
|
|
56
|
+
It's the idea itself, shipped for everyone. **Not tied to Dulus** — wrap any agent
|
|
57
|
+
and it shows up. We built the island we wanted; here it is for the whole world. 🌎
|
|
58
|
+
|
|
59
|
+
- **All platforms** — Windows, macOS, and Linux.
|
|
60
|
+
- **macOS notch-native** — on MacBooks with a camera notch the island hugs the
|
|
61
|
+
notch and stays visible across every Space and full-screen app.
|
|
62
|
+
- **Reveal on hover** — everywhere else it rests as a slim tab at the top edge and
|
|
63
|
+
slides open when your mouse approaches, or when an agent needs you.
|
|
64
|
+
- **Any agent** — anything with a CLI works through a thin wrapper; well-known
|
|
65
|
+
agents get their own icon and accent color.
|
|
66
|
+
- **100% local** — no cloud, no account, no telemetry. One WebSocket on
|
|
67
|
+
`127.0.0.1:17372`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
Requires Python 3.10+.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install dulus-bar
|
|
77
|
+
dulusbar
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
That's it — the island appears at the top of your screen. Move your mouse to the
|
|
81
|
+
top-center to reveal it; right-click the tray icon for the menu.
|
|
82
|
+
|
|
83
|
+
<details>
|
|
84
|
+
<summary><strong>From source (for the wired-agent extras)</strong></summary>
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/Dulus-Ai/Dulus-bar.git
|
|
88
|
+
cd Dulus-bar
|
|
89
|
+
|
|
90
|
+
# macOS / Linux
|
|
91
|
+
./setup.sh
|
|
92
|
+
|
|
93
|
+
# Windows
|
|
94
|
+
.\setup.ps1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> Linux tip: for click-to-focus of a terminal on X11, install `wmctrl` or `xdotool`.
|
|
98
|
+
>
|
|
99
|
+
> macOS: `setup.sh` also builds the native SwiftUI/AppKit notch surface using the
|
|
100
|
+
> Swift toolchain in Xcode Command Line Tools. The Python process stays the local
|
|
101
|
+
> WebSocket hub; Windows and Linux use the PyQt overlay.
|
|
102
|
+
|
|
103
|
+
</details>
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Wire up any AI agent — zero monopoly
|
|
108
|
+
|
|
109
|
+
Dulus Bar is **agent-agnostic**. Wrap any CLI and it reports to the island:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python wrappers/agent_wrapper.py "Codex" codex fix auth bug
|
|
113
|
+
python wrappers/agent_wrapper.py "Gemini" gemini
|
|
114
|
+
python wrappers/claude_wrapper.py # Claude Code helper
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`agent_wrapper.py "Display Name" <command> [args...]` reports that agent to the
|
|
118
|
+
bar. Known names (Claude, Codex, Gemini, Cursor, Copilot, Kimi, DeepSeek, Grok,
|
|
119
|
+
Qwen, Mistral, Windsurf, Zed, Aider, Ollama, …) get a curated icon + color;
|
|
120
|
+
anything else gets a stable auto-assigned color.
|
|
121
|
+
|
|
122
|
+
Or **right-click the island → Open agent…** and pick any executable from a file
|
|
123
|
+
dialog — Dulus Bar launches it in its own terminal, already wired.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Works beautifully with Dulus (optional)
|
|
128
|
+
|
|
129
|
+
If you run [Dulus](https://dulus.ai), the island also shows the active **model**
|
|
130
|
+
and live **context usage** — e.g. `kimi/kimi-k2.5 · ctx 38%`. But you never need
|
|
131
|
+
Dulus to use Dulus Bar. The bar is the gift; Dulus is just one of many agents it
|
|
132
|
+
speaks to.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## What it does
|
|
137
|
+
|
|
138
|
+
- Floating island, top-center (or notch-anchored on macOS).
|
|
139
|
+
- Lists active agents with a live status dot (running / waiting / done / error).
|
|
140
|
+
- When an agent asks for permission, shows **Allow / Deny** right on the island and
|
|
141
|
+
forwards your choice back to the agent over stdin.
|
|
142
|
+
- Reveals on hover, pops open on agent activity, and **stays open on a permission
|
|
143
|
+
request** until you answer.
|
|
144
|
+
- Click an agent to jump to its terminal / editor window.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## How it works
|
|
149
|
+
|
|
150
|
+
Agents connect to a tiny local WebSocket hub and send status; the island renders
|
|
151
|
+
it and relays your Allow/Deny back.
|
|
152
|
+
|
|
153
|
+
**Agent → bar:**
|
|
154
|
+
```json
|
|
155
|
+
{ "agent": "Claude", "type": "tool_request", "session_id": "abc123",
|
|
156
|
+
"payload": { "tool": "Bash", "args": "rm -rf build/" } }
|
|
157
|
+
```
|
|
158
|
+
Event types: `session_started`, `message`, `tool_request`, `tool_approved`,
|
|
159
|
+
`tool_denied`, `completed`, `error`. (`model` / `ctx` payload fields are optional.)
|
|
160
|
+
|
|
161
|
+
**Bar → agent (Allow/Deny):**
|
|
162
|
+
```json
|
|
163
|
+
{ "type": "decision", "session_id": "abc123", "payload": { "approved": true } }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
dulus_bar/
|
|
168
|
+
overlay.py Floating island UI (PyQt6), notch-aware + reveal-on-hover
|
|
169
|
+
server.py WebSocket hub (ws://127.0.0.1:17372)
|
|
170
|
+
agents.py Per-agent icon + accent color registry
|
|
171
|
+
native/ win32 / pyobjc / wmctrl window focus per platform
|
|
172
|
+
wrappers/
|
|
173
|
+
base_wrapper.py Shared: pipe stdio, detect prompts, inject Allow/Deny
|
|
174
|
+
agent_wrapper.py Generic wrapper for any CLI agent
|
|
175
|
+
macos/ Native SwiftUI/AppKit notch surface
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Build a standalone binary (optional)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install pyinstaller
|
|
184
|
+
pyinstaller --noconfirm DulusBar.spec
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Output lands in `dist/` — `DulusBar.exe` on Windows, `DulusBar.app` on macOS
|
|
188
|
+
(bundled as an `LSUIElement`, so no Dock icon).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Platform notes
|
|
193
|
+
|
|
194
|
+
- **Windows** — fully tested. `pywin32` for window focus; PyQt overlay with
|
|
195
|
+
reveal-on-hover.
|
|
196
|
+
- **macOS** — notch hugging + always-visible via `pyobjc`. Without it, still runs
|
|
197
|
+
as a plain top-most island.
|
|
198
|
+
- **Linux** — X11 window focus via `wmctrl`/`xdotool`; on Wayland the island shows
|
|
199
|
+
and stays on top, jump-to-terminal is best-effort.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
PRs welcome — new agent icons/colors, platform fixes, packaging. Open an issue,
|
|
206
|
+
fork it, ship it.
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
**MIT.** Use it, fork it, ship it. We shipped the idea on purpose — no monopoly.
|
|
211
|
+
|
|
212
|
+
<p align="center"><sub>Built by one builder in the Dominican Republic 🇩🇴 — for everyone. 🦅</sub></p>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<h1 align="center">Dulus Bar 🦅</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>A cross-platform Dynamic Island for your AI agents.</strong><br>
|
|
5
|
+
Free. Native. MIT. Works with <em>any</em> agent — no account, no cloud, no lock-in.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<code>pip install dulus-bar</code>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img alt="MIT" src="https://img.shields.io/badge/license-MIT-green">
|
|
14
|
+
<img alt="Python" src="https://img.shields.io/badge/python-3.10%2B-blue">
|
|
15
|
+
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%C2%B7%20macOS%20%C2%B7%20Linux-lightgrey">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Dulus Bar puts a floating status island at the top of your screen that shows every
|
|
21
|
+
AI coding agent you're running — Claude Code, Codex, Gemini, Cursor, Dulus, and
|
|
22
|
+
any other CLI — and lets you **Allow / Deny** their permission prompts without
|
|
23
|
+
diving back into a terminal.
|
|
24
|
+
|
|
25
|
+
It's the idea itself, shipped for everyone. **Not tied to Dulus** — wrap any agent
|
|
26
|
+
and it shows up. We built the island we wanted; here it is for the whole world. 🌎
|
|
27
|
+
|
|
28
|
+
- **All platforms** — Windows, macOS, and Linux.
|
|
29
|
+
- **macOS notch-native** — on MacBooks with a camera notch the island hugs the
|
|
30
|
+
notch and stays visible across every Space and full-screen app.
|
|
31
|
+
- **Reveal on hover** — everywhere else it rests as a slim tab at the top edge and
|
|
32
|
+
slides open when your mouse approaches, or when an agent needs you.
|
|
33
|
+
- **Any agent** — anything with a CLI works through a thin wrapper; well-known
|
|
34
|
+
agents get their own icon and accent color.
|
|
35
|
+
- **100% local** — no cloud, no account, no telemetry. One WebSocket on
|
|
36
|
+
`127.0.0.1:17372`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10+.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install dulus-bar
|
|
46
|
+
dulusbar
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it — the island appears at the top of your screen. Move your mouse to the
|
|
50
|
+
top-center to reveal it; right-click the tray icon for the menu.
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary><strong>From source (for the wired-agent extras)</strong></summary>
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/Dulus-Ai/Dulus-bar.git
|
|
57
|
+
cd Dulus-bar
|
|
58
|
+
|
|
59
|
+
# macOS / Linux
|
|
60
|
+
./setup.sh
|
|
61
|
+
|
|
62
|
+
# Windows
|
|
63
|
+
.\setup.ps1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> Linux tip: for click-to-focus of a terminal on X11, install `wmctrl` or `xdotool`.
|
|
67
|
+
>
|
|
68
|
+
> macOS: `setup.sh` also builds the native SwiftUI/AppKit notch surface using the
|
|
69
|
+
> Swift toolchain in Xcode Command Line Tools. The Python process stays the local
|
|
70
|
+
> WebSocket hub; Windows and Linux use the PyQt overlay.
|
|
71
|
+
|
|
72
|
+
</details>
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Wire up any AI agent — zero monopoly
|
|
77
|
+
|
|
78
|
+
Dulus Bar is **agent-agnostic**. Wrap any CLI and it reports to the island:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python wrappers/agent_wrapper.py "Codex" codex fix auth bug
|
|
82
|
+
python wrappers/agent_wrapper.py "Gemini" gemini
|
|
83
|
+
python wrappers/claude_wrapper.py # Claude Code helper
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`agent_wrapper.py "Display Name" <command> [args...]` reports that agent to the
|
|
87
|
+
bar. Known names (Claude, Codex, Gemini, Cursor, Copilot, Kimi, DeepSeek, Grok,
|
|
88
|
+
Qwen, Mistral, Windsurf, Zed, Aider, Ollama, …) get a curated icon + color;
|
|
89
|
+
anything else gets a stable auto-assigned color.
|
|
90
|
+
|
|
91
|
+
Or **right-click the island → Open agent…** and pick any executable from a file
|
|
92
|
+
dialog — Dulus Bar launches it in its own terminal, already wired.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Works beautifully with Dulus (optional)
|
|
97
|
+
|
|
98
|
+
If you run [Dulus](https://dulus.ai), the island also shows the active **model**
|
|
99
|
+
and live **context usage** — e.g. `kimi/kimi-k2.5 · ctx 38%`. But you never need
|
|
100
|
+
Dulus to use Dulus Bar. The bar is the gift; Dulus is just one of many agents it
|
|
101
|
+
speaks to.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## What it does
|
|
106
|
+
|
|
107
|
+
- Floating island, top-center (or notch-anchored on macOS).
|
|
108
|
+
- Lists active agents with a live status dot (running / waiting / done / error).
|
|
109
|
+
- When an agent asks for permission, shows **Allow / Deny** right on the island and
|
|
110
|
+
forwards your choice back to the agent over stdin.
|
|
111
|
+
- Reveals on hover, pops open on agent activity, and **stays open on a permission
|
|
112
|
+
request** until you answer.
|
|
113
|
+
- Click an agent to jump to its terminal / editor window.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## How it works
|
|
118
|
+
|
|
119
|
+
Agents connect to a tiny local WebSocket hub and send status; the island renders
|
|
120
|
+
it and relays your Allow/Deny back.
|
|
121
|
+
|
|
122
|
+
**Agent → bar:**
|
|
123
|
+
```json
|
|
124
|
+
{ "agent": "Claude", "type": "tool_request", "session_id": "abc123",
|
|
125
|
+
"payload": { "tool": "Bash", "args": "rm -rf build/" } }
|
|
126
|
+
```
|
|
127
|
+
Event types: `session_started`, `message`, `tool_request`, `tool_approved`,
|
|
128
|
+
`tool_denied`, `completed`, `error`. (`model` / `ctx` payload fields are optional.)
|
|
129
|
+
|
|
130
|
+
**Bar → agent (Allow/Deny):**
|
|
131
|
+
```json
|
|
132
|
+
{ "type": "decision", "session_id": "abc123", "payload": { "approved": true } }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
dulus_bar/
|
|
137
|
+
overlay.py Floating island UI (PyQt6), notch-aware + reveal-on-hover
|
|
138
|
+
server.py WebSocket hub (ws://127.0.0.1:17372)
|
|
139
|
+
agents.py Per-agent icon + accent color registry
|
|
140
|
+
native/ win32 / pyobjc / wmctrl window focus per platform
|
|
141
|
+
wrappers/
|
|
142
|
+
base_wrapper.py Shared: pipe stdio, detect prompts, inject Allow/Deny
|
|
143
|
+
agent_wrapper.py Generic wrapper for any CLI agent
|
|
144
|
+
macos/ Native SwiftUI/AppKit notch surface
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Build a standalone binary (optional)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install pyinstaller
|
|
153
|
+
pyinstaller --noconfirm DulusBar.spec
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Output lands in `dist/` — `DulusBar.exe` on Windows, `DulusBar.app` on macOS
|
|
157
|
+
(bundled as an `LSUIElement`, so no Dock icon).
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Platform notes
|
|
162
|
+
|
|
163
|
+
- **Windows** — fully tested. `pywin32` for window focus; PyQt overlay with
|
|
164
|
+
reveal-on-hover.
|
|
165
|
+
- **macOS** — notch hugging + always-visible via `pyobjc`. Without it, still runs
|
|
166
|
+
as a plain top-most island.
|
|
167
|
+
- **Linux** — X11 window focus via `wmctrl`/`xdotool`; on Wayland the island shows
|
|
168
|
+
and stays on top, jump-to-terminal is best-effort.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Contributing
|
|
173
|
+
|
|
174
|
+
PRs welcome — new agent icons/colors, platform fixes, packaging. Open an issue,
|
|
175
|
+
fork it, ship it.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
**MIT.** Use it, fork it, ship it. We shipped the idea on purpose — no monopoly.
|
|
180
|
+
|
|
181
|
+
<p align="center"><sub>Built by one builder in the Dominican Republic 🇩🇴 — for everyone. 🦅</sub></p>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Agent catalog — per-agent identity (icon + accent color) for any AI CLI.
|
|
2
|
+
|
|
3
|
+
Dulus Bar is agent-agnostic: it will happily show *any* agent name a wrapper
|
|
4
|
+
reports. This module just gives the well-known ones a recognizable emoji and
|
|
5
|
+
accent color, and hands unknown agents a stable, nicely-distributed color so
|
|
6
|
+
the UI still looks intentional. It is presentation-only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import shutil
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Dict, List, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class AgentStyle:
|
|
18
|
+
key: str
|
|
19
|
+
display: str
|
|
20
|
+
emoji: str
|
|
21
|
+
accent: str # hex
|
|
22
|
+
aliases: tuple = ()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Curated identities. `accent` roughly follows each brand.
|
|
26
|
+
_AGENTS: List[AgentStyle] = [
|
|
27
|
+
AgentStyle("dulus", "Dulus", "🦅", "#f59e0b", ("dulus", "interant")),
|
|
28
|
+
AgentStyle("claude", "Claude Code", "✳️", "#d97757", ("claude", "claude code", "anthropic")),
|
|
29
|
+
AgentStyle("codex", "Codex", "◆", "#10a37f", ("codex", "openai", "gpt")),
|
|
30
|
+
AgentStyle("gemini", "Gemini", "✦", "#4285f4", ("gemini", "google")),
|
|
31
|
+
AgentStyle("cursor", "Cursor", "▸", "#7c3aed", ("cursor",)),
|
|
32
|
+
AgentStyle("copilot", "Copilot", "⧉", "#6e7681", ("copilot", "github copilot")),
|
|
33
|
+
AgentStyle("kimi", "Kimi", "◐", "#2563eb", ("kimi", "moonshot")),
|
|
34
|
+
AgentStyle("deepseek", "DeepSeek", "≋", "#4d6bfe", ("deepseek",)),
|
|
35
|
+
AgentStyle("grok", "Grok", "𝕏", "#111827", ("grok", "xai")),
|
|
36
|
+
AgentStyle("qwen", "Qwen", "✺", "#615ced", ("qwen", "alibaba", "tongyi")),
|
|
37
|
+
AgentStyle("mistral", "Mistral", "◢", "#fa5010", ("mistral", "codestral")),
|
|
38
|
+
AgentStyle("windsurf", "Windsurf", "≈", "#09b6a2", ("windsurf", "codeium")),
|
|
39
|
+
AgentStyle("zed", "Zed", "⚡", "#084ccf", ("zed",)),
|
|
40
|
+
AgentStyle("aider", "Aider", "✎", "#22c55e", ("aider",)),
|
|
41
|
+
AgentStyle("cline", "Cline", "◔", "#0ea5e9", ("cline", "roo")),
|
|
42
|
+
AgentStyle("ollama", "Ollama", "◍", "#000000", ("ollama", "llama")),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
_BY_ALIAS: Dict[str, AgentStyle] = {}
|
|
46
|
+
for _a in _AGENTS:
|
|
47
|
+
_BY_ALIAS[_a.key] = _a
|
|
48
|
+
for _al in _a.aliases:
|
|
49
|
+
_BY_ALIAS[_al] = _a
|
|
50
|
+
|
|
51
|
+
# Fallback palette for unknown agents (kept colorblind-distinct-ish).
|
|
52
|
+
_FALLBACK_PALETTE = (
|
|
53
|
+
"#38bdf8",
|
|
54
|
+
"#a78bfa",
|
|
55
|
+
"#34d399",
|
|
56
|
+
"#fbbf24",
|
|
57
|
+
"#fb7185",
|
|
58
|
+
"#e879f9",
|
|
59
|
+
"#5eead4",
|
|
60
|
+
"#93c5fd",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def style_for(agent_name: str) -> AgentStyle:
|
|
65
|
+
"""Return an AgentStyle for any agent name (never raises)."""
|
|
66
|
+
name = (agent_name or "").strip()
|
|
67
|
+
low = name.lower()
|
|
68
|
+
if low in _BY_ALIAS:
|
|
69
|
+
return _BY_ALIAS[low]
|
|
70
|
+
for alias, style in _BY_ALIAS.items():
|
|
71
|
+
if alias in low:
|
|
72
|
+
return style
|
|
73
|
+
# Deterministic fallback color from the name.
|
|
74
|
+
idx = sum(ord(c) for c in low) % len(_FALLBACK_PALETTE) if low else 0
|
|
75
|
+
return AgentStyle(
|
|
76
|
+
key=low or "agent",
|
|
77
|
+
display=name or "Agent",
|
|
78
|
+
emoji="●",
|
|
79
|
+
accent=_FALLBACK_PALETTE[idx],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def is_dulus(agent_name: str) -> bool:
|
|
84
|
+
return style_for(agent_name).key == "dulus"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# CLI executable names for known agents, used by "quick launch" in the UI.
|
|
88
|
+
# The first name found on PATH wins.
|
|
89
|
+
_LAUNCH_COMMANDS: Dict[str, Tuple[str, ...]] = {
|
|
90
|
+
"claude": ("claude",),
|
|
91
|
+
"codex": ("codex",),
|
|
92
|
+
"gemini": ("gemini",),
|
|
93
|
+
"cursor": ("cursor-agent", "cursor"),
|
|
94
|
+
"aider": ("aider",),
|
|
95
|
+
"qwen": ("qwen",),
|
|
96
|
+
"grok": ("grok",),
|
|
97
|
+
"copilot": ("copilot",),
|
|
98
|
+
"opencode": ("opencode",),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def detect_installed() -> List[Tuple[AgentStyle, str]]:
|
|
103
|
+
"""Return (style, resolved_executable) for known agents found on PATH."""
|
|
104
|
+
found: List[Tuple[AgentStyle, str]] = []
|
|
105
|
+
for key, exe_names in _LAUNCH_COMMANDS.items():
|
|
106
|
+
for exe in exe_names:
|
|
107
|
+
path = shutil.which(exe)
|
|
108
|
+
if path:
|
|
109
|
+
found.append((_BY_ALIAS[key], path))
|
|
110
|
+
break
|
|
111
|
+
return found
|
|
Binary file
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Lifecycle bridge for the native SwiftUI/AppKit notch surface on macOS."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from .server import AgentEventServer
|
|
15
|
+
|
|
16
|
+
WS_PORT = 17372
|
|
17
|
+
_NATIVE_EXECUTABLE = "DulusBarNative"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def project_root() -> Path:
|
|
21
|
+
return Path(__file__).resolve().parent.parent
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def native_binary(root: Optional[Path] = None) -> Path:
|
|
25
|
+
"""Return the preferred release helper path."""
|
|
26
|
+
base = root or project_root()
|
|
27
|
+
override = os.environ.get("DULUS_BAR_NATIVE_BINARY")
|
|
28
|
+
if override:
|
|
29
|
+
return Path(override).expanduser().resolve()
|
|
30
|
+
return base / "macos" / ".build" / "release" / _NATIVE_EXECUTABLE
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def native_available(root: Optional[Path] = None) -> bool:
|
|
34
|
+
path = native_binary(root)
|
|
35
|
+
return path.is_file() and os.access(path, os.X_OK)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_native(root: Optional[Path] = None) -> Path:
|
|
39
|
+
"""Build the native helper and return its executable path."""
|
|
40
|
+
base = root or project_root()
|
|
41
|
+
package = base / "macos"
|
|
42
|
+
if not (package / "Package.swift").is_file():
|
|
43
|
+
raise FileNotFoundError(f"native package missing: {package}")
|
|
44
|
+
subprocess.run(
|
|
45
|
+
["swift", "build", "-c", "release"],
|
|
46
|
+
cwd=package,
|
|
47
|
+
check=True,
|
|
48
|
+
)
|
|
49
|
+
binary = native_binary(base)
|
|
50
|
+
if not binary.is_file():
|
|
51
|
+
raise FileNotFoundError(f"native build completed without {binary}")
|
|
52
|
+
return binary
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _terminate(process: subprocess.Popen) -> None:
|
|
56
|
+
if process.poll() is not None:
|
|
57
|
+
return
|
|
58
|
+
process.terminate()
|
|
59
|
+
try:
|
|
60
|
+
process.wait(timeout=3)
|
|
61
|
+
except subprocess.TimeoutExpired:
|
|
62
|
+
process.kill()
|
|
63
|
+
process.wait(timeout=2)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_native() -> int:
|
|
67
|
+
"""Own the WebSocket hub and run the native helper until it exits."""
|
|
68
|
+
binary = native_binary()
|
|
69
|
+
if not native_available():
|
|
70
|
+
if os.environ.get("DULUS_BAR_NATIVE_AUTOBUILD", "1") == "0":
|
|
71
|
+
raise FileNotFoundError(f"native helper not built: {binary}")
|
|
72
|
+
print("[dulusbar] native helper missing; building release binary...")
|
|
73
|
+
binary = build_native()
|
|
74
|
+
|
|
75
|
+
server = AgentEventServer(port=WS_PORT)
|
|
76
|
+
server.start()
|
|
77
|
+
process = subprocess.Popen(
|
|
78
|
+
[str(binary), "--websocket", f"ws://127.0.0.1:{WS_PORT}"],
|
|
79
|
+
cwd=project_root(),
|
|
80
|
+
env={**os.environ, "DULUS_BAR_PARENT_PID": str(os.getpid())},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
cleaned = False
|
|
84
|
+
|
|
85
|
+
def cleanup() -> None:
|
|
86
|
+
nonlocal cleaned
|
|
87
|
+
if cleaned:
|
|
88
|
+
return
|
|
89
|
+
cleaned = True
|
|
90
|
+
_terminate(process)
|
|
91
|
+
server.stop()
|
|
92
|
+
|
|
93
|
+
def handle_signal(signum: int, _frame: object) -> None:
|
|
94
|
+
cleanup()
|
|
95
|
+
raise SystemExit(128 + signum)
|
|
96
|
+
|
|
97
|
+
atexit.register(cleanup)
|
|
98
|
+
previous_handlers = {}
|
|
99
|
+
for signum in (signal.SIGINT, signal.SIGTERM):
|
|
100
|
+
previous_handlers[signum] = signal.getsignal(signum)
|
|
101
|
+
signal.signal(signum, handle_signal)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
while True:
|
|
105
|
+
code = process.poll()
|
|
106
|
+
if code is not None:
|
|
107
|
+
return code
|
|
108
|
+
time.sleep(0.25)
|
|
109
|
+
finally:
|
|
110
|
+
cleanup()
|
|
111
|
+
for signum, handler in previous_handlers.items():
|
|
112
|
+
signal.signal(signum, handler)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def should_use_native() -> bool:
|
|
116
|
+
if sys.platform != "darwin":
|
|
117
|
+
return False
|
|
118
|
+
return os.environ.get("DULUS_BAR_FORCE_QT", "0").lower() not in {"1", "true", "yes"}
|