huebox 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.
- huebox-0.3.0/LICENSE +21 -0
- huebox-0.3.0/PKG-INFO +310 -0
- huebox-0.3.0/README.md +285 -0
- huebox-0.3.0/huebox/__init__.py +34 -0
- huebox-0.3.0/huebox/__main__.py +7 -0
- huebox-0.3.0/huebox/app.py +1949 -0
- huebox-0.3.0/huebox/cli.py +597 -0
- huebox-0.3.0/huebox/color.py +76 -0
- huebox-0.3.0/huebox/detect.py +369 -0
- huebox-0.3.0/huebox/editor.py +1502 -0
- huebox-0.3.0/huebox/formats/__init__.py +42 -0
- huebox-0.3.0/huebox/formats/alacritty.py +97 -0
- huebox-0.3.0/huebox/formats/base.py +98 -0
- huebox-0.3.0/huebox/formats/ghostty.py +17 -0
- huebox-0.3.0/huebox/formats/kitty.py +20 -0
- huebox-0.3.0/huebox/render.py +1066 -0
- huebox-0.3.0/huebox/themes.py +860 -0
- huebox-0.3.0/huebox/tui.py +35 -0
- huebox-0.3.0/huebox.egg-info/PKG-INFO +310 -0
- huebox-0.3.0/huebox.egg-info/SOURCES.txt +33 -0
- huebox-0.3.0/huebox.egg-info/dependency_links.txt +1 -0
- huebox-0.3.0/huebox.egg-info/entry_points.txt +2 -0
- huebox-0.3.0/huebox.egg-info/requires.txt +5 -0
- huebox-0.3.0/huebox.egg-info/top_level.txt +1 -0
- huebox-0.3.0/pyproject.toml +37 -0
- huebox-0.3.0/setup.cfg +4 -0
- huebox-0.3.0/tests/test_app.py +2129 -0
- huebox-0.3.0/tests/test_cli.py +57 -0
- huebox-0.3.0/tests/test_closure.py +322 -0
- huebox-0.3.0/tests/test_detect.py +444 -0
- huebox-0.3.0/tests/test_editor.py +2392 -0
- huebox-0.3.0/tests/test_formats.py +180 -0
- huebox-0.3.0/tests/test_render.py +1037 -0
- huebox-0.3.0/tests/test_themes.py +1903 -0
- huebox-0.3.0/tests/test_tui.py +59 -0
huebox-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikkel Kappel Persson
|
|
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.
|
huebox-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: huebox
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A terminal theme editor with live preview
|
|
5
|
+
Author-email: Mikkel Kappel Persson <mikkel@kappelpersson.dk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MikkelKappelPersson/huebox
|
|
8
|
+
Project-URL: Repository, https://github.com/MikkelKappelPersson/huebox
|
|
9
|
+
Project-URL: Issues, https://github.com/MikkelKappelPersson/huebox/issues
|
|
10
|
+
Keywords: terminal,theme,color,colour,tui,ghostty,kitty,alacritty
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Terminals
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: Pygments>=2.0
|
|
21
|
+
Requires-Dist: textual>=8
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pyte>=0.8; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# huebox
|
|
27
|
+
|
|
28
|
+
A terminal theme editor with live preview.
|
|
29
|
+
|
|
30
|
+
`huebox` shows you the colours your terminal is actually using, and lets you
|
|
31
|
+
change them from a keyboard-driven interface — no hex codes, no hand-editing,
|
|
32
|
+
no restarting.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
huebox TUI when stdout is a terminal, static preview otherwise
|
|
36
|
+
huebox edit [name] force the interactive editor (a theme if named)
|
|
37
|
+
huebox show [name] force the static preview (of a theme if named)
|
|
38
|
+
huebox --dump [name] print the resolved colours and exit
|
|
39
|
+
huebox --formats list supported formats
|
|
40
|
+
huebox new <name> create a theme from your terminal (or a built-in ramp)
|
|
41
|
+
huebox list list your themes, current one marked
|
|
42
|
+
huebox use <name> make a theme current and push it to your terminal
|
|
43
|
+
huebox import <name> snapshot the detected terminal into a theme
|
|
44
|
+
|
|
45
|
+
Flags on top of the v1 set: `--to ghostty,kitty` chooses push targets,
|
|
46
|
+
`--no-push` writes the theme file only, `--no-reload` leaves the terminal's
|
|
47
|
+
own reload to you, `--ghostty-in-place` keeps a Ghostty
|
|
48
|
+
push in the file the colours already live in, `--force` replaces a theme
|
|
49
|
+
`new` / `import` would not overwrite, `--from <fmt>` names the terminal to
|
|
50
|
+
read from.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
git clone https://github.com/MikkelKappelPersson/huebox
|
|
57
|
+
cd huebox
|
|
58
|
+
uv tool install . # or: pipx install . / pip install --user .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Two dependencies beyond the stdlib, both installed automatically: [Pygments](https://pygments.org),
|
|
62
|
+
which powers the editor's live code sample, and [Textual](https://textual.textualize.io/),
|
|
63
|
+
which the interactive editor runs on. Every colour in the sample comes from
|
|
64
|
+
the theme's own palette.
|
|
65
|
+
|
|
66
|
+
## What's new in 0.2
|
|
67
|
+
|
|
68
|
+
**Your themes live in one place, not in three configs.** `~/.config/huebox` is
|
|
69
|
+
the truth: `huebox import dusk` snapshots a terminal into a theme,
|
|
70
|
+
`huebox edit dusk` works on it, and every save or `huebox use dusk` pushes it
|
|
71
|
+
back to the terminal you are in. `t` inside the editor switches themes, `N`
|
|
72
|
+
turns a direct-config session into a library one, and a Ghostty config that
|
|
73
|
+
keeps its colours in a theme file is pushed there rather than into whatever
|
|
74
|
+
file it happens to point at. Editing is staged too: keystrokes live in a
|
|
75
|
+
buffer, `Ctrl+S` is the only write.
|
|
76
|
+
|
|
77
|
+
Five changes you might notice on upgrade:
|
|
78
|
+
|
|
79
|
+
- A push now ends with your terminal reloading its config, so the colours
|
|
80
|
+
are live when the command finishes, and opening a theme in the picker
|
|
81
|
+
(`t`, `Enter`) saves and pushes it instead of only loading it into the
|
|
82
|
+
buffer. `--no-reload` turns the reload off.
|
|
83
|
+
- If your Ghostty config has a `theme =` line, saving a huebox theme now
|
|
84
|
+
writes that theme's own file under `~/.config/ghostty/themes/` and swaps
|
|
85
|
+
that one line, where before it spliced the colours into whichever theme
|
|
86
|
+
file the config pointed at — so saving a theme named `test` used to
|
|
87
|
+
rewrite `Nightspice`. A `theme =` naming a file that is missing is now
|
|
88
|
+
repaired by the save rather than refused. Configs with inline colours are
|
|
89
|
+
edited in place as before; `--ghostty-in-place` restores the old
|
|
90
|
+
behaviour where it is safe to, and refuses it where it is not.
|
|
91
|
+
- kitty's config-path override is `KITTY_CONFIG_DIRECTORY`, which is what
|
|
92
|
+
kitty itself reads. The old `KITTY_CONFIG_DIR` spelling still works for one
|
|
93
|
+
more release, and is probed second.
|
|
94
|
+
- `ALACRITTY_CONFIG_DIR` and `ALACRITTY_CONFIG` are gone — Alacritty documents
|
|
95
|
+
no such variable, so they never pointed at anything Alacritty read. Use
|
|
96
|
+
`--config`.
|
|
97
|
+
- Alacritty configs are now also looked for at
|
|
98
|
+
`$XDG_CONFIG_HOME/alacritty.toml`, which is the second path Alacritty's own
|
|
99
|
+
search order checks. The legacy `alacritty.yml` still counts.
|
|
100
|
+
|
|
101
|
+
Nothing above changes what huebox writes to a config: colours only, line by
|
|
102
|
+
line — and a save that changes nothing no longer even touches the file, so its
|
|
103
|
+
mtime survives too.
|
|
104
|
+
|
|
105
|
+
## Themes
|
|
106
|
+
|
|
107
|
+
`~/.config/huebox` is the truth: one file per theme in `themes/<name>.toml`,
|
|
108
|
+
plus `state.toml` naming the one you are working on. A terminal config is a
|
|
109
|
+
push target, not the place your theme lives.
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
huebox import dusk # snapshot this terminal into a theme
|
|
113
|
+
huebox edit dusk # edit it; every Ctrl+S saves and pushes
|
|
114
|
+
huebox use dusk # switch to it and push, no editor
|
|
115
|
+
huebox use dusk --to ghostty,kitty # push to specific terminals
|
|
116
|
+
huebox use dusk --no-push # switch without touching a config
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`huebox edit` with no name opens the current theme; `huebox show <name>` and
|
|
120
|
+
`huebox --dump <name>` read a theme file instead of a config. With no theme
|
|
121
|
+
library at all, `huebox edit` is what it always was: editing the terminal
|
|
122
|
+
config in place.
|
|
123
|
+
|
|
124
|
+
## Editing
|
|
125
|
+
|
|
126
|
+
Run `huebox edit` and drive it with the keyboard.
|
|
127
|
+
|
|
128
|
+
| Key | Action |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| arrows | move between slots — along a row, or up/down a row, in the grid on screen |
|
|
131
|
+
| `q` / `w` | hue −/+ |
|
|
132
|
+
| `a` / `s` | saturation −/+ |
|
|
133
|
+
| `z` / `x` | lightness −/+ |
|
|
134
|
+
| `f` | cycle step size ×1 → ×5 → ×20 |
|
|
135
|
+
| `i` | type a hex value |
|
|
136
|
+
| `Ctrl+S` | save — the session's only write: the theme file, then a push |
|
|
137
|
+
| `u` / `r` | undo / revert to the last save |
|
|
138
|
+
| `t` | theme picker — arrows, `Enter` opens, `n` makes a theme from the buffer, `Esc` back |
|
|
139
|
+
| `N` | save the buffer as a new theme (and then save it) |
|
|
140
|
+
| `Esc` | quit — twice if there are unsaved changes |
|
|
141
|
+
|
|
142
|
+
The mouse works too: click a swatch or interface cell to select it, click a
|
|
143
|
+
picker row to open it, wheel through a long picker list, and drag across the
|
|
144
|
+
code sample or diff to select text for copying. Clicking anywhere else — the
|
|
145
|
+
header, the hints, the empty air — does nothing, and the wheel does nothing
|
|
146
|
+
outside the picker.
|
|
147
|
+
|
|
148
|
+
Edits live in an in-memory buffer: nothing is written until you press
|
|
149
|
+
`Ctrl+S`. The editor *renders* from that buffer, so everything on screen —
|
|
150
|
+
palette, interface, code sample, and the background / selection / cursor
|
|
151
|
+
examples — is live and truecolor before the file changes (a frame with room to
|
|
152
|
+
spare also shows a git diff of the sample). The frame's own text is drawn
|
|
153
|
+
from the buffer too: the `huebox` wordmark at the top wears the six bright
|
|
154
|
+
hues one letter each, a key in the hint line is bright (`arrows`), the label
|
|
155
|
+
beside it is teal (**move**), the furniture — a path, a hex, a count — is
|
|
156
|
+
muted, and a section header is the theme's own foreground in bold. Edit
|
|
157
|
+
`palette-11` and both the escape in the sample and the keys change colour on
|
|
158
|
+
the same frame. The floor is the buffer's too: every row of the editor —
|
|
159
|
+
the ground under the palette grid, the air between the widgets, the column
|
|
160
|
+
after the last hint — is painted in `background`, so the frame is a sample
|
|
161
|
+
of the theme rather than a preview beside it.
|
|
162
|
+
|
|
163
|
+
The `selected` row ends in a reading of the slot's own colour: for each axis a
|
|
164
|
+
number and a bar — `hue 120° [the whole wheel] sat 48% [grey → colour] val
|
|
165
|
+
48% [black → colour]`. Each reading sits in a fixed-width field, so the bars
|
|
166
|
+
start in the same columns whatever the slot says and the row does not jump when
|
|
167
|
+
a number grows a digit. The bar is a sweep of its whole axis at the slot's own
|
|
168
|
+
other two readings — press `a`/`s` or `z`/`x` and the entire hue wheel repaints
|
|
169
|
+
— the number says what the reading is, and a hairline one eighth of a cell
|
|
170
|
+
wide, set into the cell the reading falls nearest, says where it sits on the
|
|
171
|
+
bar. The exact reading — `hue 120.0 sat 48.4% val 47.8%` — sits on the row
|
|
172
|
+
below beside the specimen: the bars and the numbers are the glance, the exact
|
|
173
|
+
numbers are the truth, and neither gives up a row for the other.
|
|
174
|
+
|
|
175
|
+
Quit with unsaved changes and huebox asks for a second `Esc`
|
|
176
|
+
first; `r` throws the
|
|
177
|
+
buffer away and goes back to your last save.
|
|
178
|
+
|
|
179
|
+
The header names what you are editing: `ember ● ghostty` for a theme (the `●`
|
|
180
|
+
marks unsaved buffer changes) or `direct:/path/to/config` in a legacy
|
|
181
|
+
direct-config session. `t` opens the theme picker without leaving the editor:
|
|
182
|
+
arrows and `Enter` to use a theme, `n` to make one from the buffer you are
|
|
183
|
+
looking at, `Esc` to go back. `Enter` is a save, not a peek — it writes the
|
|
184
|
+
theme, pushes it and reloads your terminal, so the theme you pick is the one
|
|
185
|
+
on screen; `Ctrl+S` stays for the buffer's own edits. Opening a theme while
|
|
186
|
+
the buffer has unsaved edits is refused with
|
|
187
|
+
`save (Ctrl+S) or revert (r) first` rather than losing them. `N` is the way
|
|
188
|
+
out of a direct-config session: it asks for a name, makes the theme, makes it
|
|
189
|
+
current, and saves it through the same pipeline. If a name is already taken,
|
|
190
|
+
huebox says so and waits for `y` (overwrite) or another name — no modal, and
|
|
191
|
+
nothing is written until you answer.
|
|
192
|
+
|
|
193
|
+
`Ctrl+S` is two writes: the theme file first, then a push into the terminal
|
|
194
|
+
config that holds your colours — the status bar says which
|
|
195
|
+
(`saved dusk → ghostty`). The theme file is the truth and is never rolled
|
|
196
|
+
back: if a push fails, the save still stands, huebox says why on stderr, and
|
|
197
|
+
the session ends with exit 1. Editing a terminal config directly (no themes
|
|
198
|
+
yet) takes a `<config>.huebox.bak` on its first save; theme files get none.
|
|
199
|
+
|
|
200
|
+
A push that lands asks the terminal to re-read its config, so the new colours
|
|
201
|
+
are live when the command finishes: ghostty is signalled the way `Ctrl+Shift+,`
|
|
202
|
+
signals it and kitty is asked over its own remote control. A terminal that
|
|
203
|
+
cannot be told keeps the advice line in the report — `Ctrl+Shift+,` for
|
|
204
|
+
ghostty, `Ctrl+Shift+F5` for kitty, and alacritty picks changes up by itself.
|
|
205
|
+
`--no-reload` turns the whole step off. The code sample inside the editor is
|
|
206
|
+
rendered in truecolor from the values you are editing, so it updates *before*
|
|
207
|
+
the reload. A tall enough frame also draws a
|
|
208
|
+
git diff of that sample — `+` lines wear palette 2, `-` lines palette 1 — so a
|
|
209
|
+
theme whose red and green are wrong says so before you reload; the hunk is
|
|
210
|
+
drawn only out of rows the sample did not need, so it never costs it a line.
|
|
211
|
+
|
|
212
|
+
A config is only ever edited line by line, so a colour the config does not
|
|
213
|
+
define is reported (`not carried by this config: cursor-text, …`) and left
|
|
214
|
+
alone — huebox will not invent a line in your terminal's config. A Ghostty
|
|
215
|
+
theme file is the one file huebox writes whole, and only because it is
|
|
216
|
+
named after the theme it holds.
|
|
217
|
+
|
|
218
|
+
### Ghostty themes
|
|
219
|
+
|
|
220
|
+
Ghostty keeps its colours in theme files, and a save joins it there whenever
|
|
221
|
+
your config is already organised that way — when it has a `theme =` line,
|
|
222
|
+
huebox writes the theme under *its own* name and repoints your config at
|
|
223
|
+
it:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
huebox use dusk --to ghostty # or edit dusk + Ctrl+S
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
That writes `~/.config/ghostty/themes/dusk` — 22 colours, in Ghostty's own
|
|
230
|
+
`palette = 0=#…` spelling, read back by huebox without drift — and points
|
|
231
|
+
your main config at it with a single `theme =` line: an existing one keeps
|
|
232
|
+
its spacing, its quotes and its comment and only the value changes, a config
|
|
233
|
+
without one gets the line appended, and every other byte of the file is left
|
|
234
|
+
exactly as it was. **Your previous theme file is not touched.** A theme's
|
|
235
|
+
colours never land in a file that belongs to another theme, which is the
|
|
236
|
+
whole reason the save goes through a file at all: saving `dusk` while your
|
|
237
|
+
config is on `Nightspice` gives you a `dusk` file and moves one line, where
|
|
238
|
+
before it would have rewritten `Nightspice` under a name that was no longer
|
|
239
|
+
true. The themes directory is shared with Ghostty's built-ins, so an export
|
|
240
|
+
overwrites a same-name file there (the report tells you when it did).
|
|
241
|
+
|
|
242
|
+
If your config holds its colours inline — or in a `config-file` include —
|
|
243
|
+
there is no theme name in play, so huebox edits the file the colours are
|
|
244
|
+
already in and leaves your layout alone. Two flags say it out loud:
|
|
245
|
+
`--ghostty-native` forces the export even there (adding the `theme =` line
|
|
246
|
+
for you), `--ghostty-in-place` forces the edit; asking for both is refused.
|
|
247
|
+
The forced export is the one case that can leave a colour behind a theme
|
|
248
|
+
file, and the report says so when it does. Both apply to the ghostty target
|
|
249
|
+
only — `--to ghostty,kitty` exports for Ghostty and pushes kitty the
|
|
250
|
+
ordinary way — and `--no-push` wins over either.
|
|
251
|
+
|
|
252
|
+
A `theme =` line pointing at a file that is not there (Ghostty calls that
|
|
253
|
+
a configuration error on reload) is treated as a broken config, not a
|
|
254
|
+
colourless one: `huebox use <name>` writes the file, fixes the pointer, and
|
|
255
|
+
tells you the file was missing. Nothing is overwritten to do it — the file
|
|
256
|
+
did not exist.
|
|
257
|
+
|
|
258
|
+
## Supported terminals
|
|
259
|
+
|
|
260
|
+
| Format | Config |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| Ghostty | `$XDG_CONFIG_HOME/ghostty/config.ghostty`, including `config-file` includes and `theme = Name` indirection |
|
|
263
|
+
| kitty | `$XDG_CONFIG_HOME/kitty/kitty.conf`, `~/.kitty.conf`, or `KITTY_CONFIG_DIRECTORY` |
|
|
264
|
+
| Alacritty | `$XDG_CONFIG_HOME/alacritty/alacritty.toml`, `$XDG_CONFIG_HOME/alacritty.toml`, `~/.alacritty.toml` (and the legacy `alacritty.yml`), dotted keys, `[section]` tables and inline tables |
|
|
265
|
+
|
|
266
|
+
The search order is each terminal's own (upstream docs, checked October
|
|
267
|
+
2026). `~/.config/...` paths follow `XDG_CONFIG_HOME` when you set it. Alacritty
|
|
268
|
+
documents no environment variable for its config path, so `--config` is the
|
|
269
|
+
only override there; for kitty, `KITTY_CONFIG_DIRECTORY` is upstream's
|
|
270
|
+
spelling (the old `KITTY_CONFIG_DIR` still works, deprecated, for one
|
|
271
|
+
release).
|
|
272
|
+
|
|
273
|
+
huebox only offers a terminal whose config actually contains colours, so a
|
|
274
|
+
leftover `ALACRITTY_SOCKET` from a session last week will not hijack your
|
|
275
|
+
Ghostty config — and it will not push into one either. Override the guess with
|
|
276
|
+
`--format`:
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
huebox edit --format kitty
|
|
280
|
+
huebox show --config ~/dotfiles/alacritty.toml
|
|
281
|
+
huebox use ember --to ghostty --config ~/dotfiles/ghostty/config
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`--config` pushes one format only: with a single `--to` it names the file,
|
|
285
|
+
with several `--to` targets it is refused (ambiguity, not a guess).
|
|
286
|
+
|
|
287
|
+
## Safety
|
|
288
|
+
|
|
289
|
+
Writes are line-level: only the colour tokens are replaced, so comments,
|
|
290
|
+
ordering, alignment and every unrelated setting survive untouched. A save that
|
|
291
|
+
changes nothing is not just byte-identical, it does not write the file at all —
|
|
292
|
+
its mtime is untouched too, so a backup job or a config manager never notices a
|
|
293
|
+
save that saved nothing. Pushing a theme uses that same writer, so a push is no
|
|
294
|
+
more invasive than the v1 in-place edit. The only file huebox writes outside
|
|
295
|
+
your terminal config is its own `~/.config/huebox` library.
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
python3 -m unittest discover -s tests
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The tests cover reading every slot, round-tripping without drift, changing
|
|
304
|
+
only the intended lines, leaving a config untouched (bytes *and* mtime) when
|
|
305
|
+
nothing changed, following Ghostty includes and `theme =` pointers, pushing
|
|
306
|
+
without inventing keys, and keeping the layout inside narrow terminals.
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|
huebox-0.3.0/README.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# huebox
|
|
2
|
+
|
|
3
|
+
A terminal theme editor with live preview.
|
|
4
|
+
|
|
5
|
+
`huebox` shows you the colours your terminal is actually using, and lets you
|
|
6
|
+
change them from a keyboard-driven interface — no hex codes, no hand-editing,
|
|
7
|
+
no restarting.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
huebox TUI when stdout is a terminal, static preview otherwise
|
|
11
|
+
huebox edit [name] force the interactive editor (a theme if named)
|
|
12
|
+
huebox show [name] force the static preview (of a theme if named)
|
|
13
|
+
huebox --dump [name] print the resolved colours and exit
|
|
14
|
+
huebox --formats list supported formats
|
|
15
|
+
huebox new <name> create a theme from your terminal (or a built-in ramp)
|
|
16
|
+
huebox list list your themes, current one marked
|
|
17
|
+
huebox use <name> make a theme current and push it to your terminal
|
|
18
|
+
huebox import <name> snapshot the detected terminal into a theme
|
|
19
|
+
|
|
20
|
+
Flags on top of the v1 set: `--to ghostty,kitty` chooses push targets,
|
|
21
|
+
`--no-push` writes the theme file only, `--no-reload` leaves the terminal's
|
|
22
|
+
own reload to you, `--ghostty-in-place` keeps a Ghostty
|
|
23
|
+
push in the file the colours already live in, `--force` replaces a theme
|
|
24
|
+
`new` / `import` would not overwrite, `--from <fmt>` names the terminal to
|
|
25
|
+
read from.
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
git clone https://github.com/MikkelKappelPersson/huebox
|
|
32
|
+
cd huebox
|
|
33
|
+
uv tool install . # or: pipx install . / pip install --user .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Two dependencies beyond the stdlib, both installed automatically: [Pygments](https://pygments.org),
|
|
37
|
+
which powers the editor's live code sample, and [Textual](https://textual.textualize.io/),
|
|
38
|
+
which the interactive editor runs on. Every colour in the sample comes from
|
|
39
|
+
the theme's own palette.
|
|
40
|
+
|
|
41
|
+
## What's new in 0.2
|
|
42
|
+
|
|
43
|
+
**Your themes live in one place, not in three configs.** `~/.config/huebox` is
|
|
44
|
+
the truth: `huebox import dusk` snapshots a terminal into a theme,
|
|
45
|
+
`huebox edit dusk` works on it, and every save or `huebox use dusk` pushes it
|
|
46
|
+
back to the terminal you are in. `t` inside the editor switches themes, `N`
|
|
47
|
+
turns a direct-config session into a library one, and a Ghostty config that
|
|
48
|
+
keeps its colours in a theme file is pushed there rather than into whatever
|
|
49
|
+
file it happens to point at. Editing is staged too: keystrokes live in a
|
|
50
|
+
buffer, `Ctrl+S` is the only write.
|
|
51
|
+
|
|
52
|
+
Five changes you might notice on upgrade:
|
|
53
|
+
|
|
54
|
+
- A push now ends with your terminal reloading its config, so the colours
|
|
55
|
+
are live when the command finishes, and opening a theme in the picker
|
|
56
|
+
(`t`, `Enter`) saves and pushes it instead of only loading it into the
|
|
57
|
+
buffer. `--no-reload` turns the reload off.
|
|
58
|
+
- If your Ghostty config has a `theme =` line, saving a huebox theme now
|
|
59
|
+
writes that theme's own file under `~/.config/ghostty/themes/` and swaps
|
|
60
|
+
that one line, where before it spliced the colours into whichever theme
|
|
61
|
+
file the config pointed at — so saving a theme named `test` used to
|
|
62
|
+
rewrite `Nightspice`. A `theme =` naming a file that is missing is now
|
|
63
|
+
repaired by the save rather than refused. Configs with inline colours are
|
|
64
|
+
edited in place as before; `--ghostty-in-place` restores the old
|
|
65
|
+
behaviour where it is safe to, and refuses it where it is not.
|
|
66
|
+
- kitty's config-path override is `KITTY_CONFIG_DIRECTORY`, which is what
|
|
67
|
+
kitty itself reads. The old `KITTY_CONFIG_DIR` spelling still works for one
|
|
68
|
+
more release, and is probed second.
|
|
69
|
+
- `ALACRITTY_CONFIG_DIR` and `ALACRITTY_CONFIG` are gone — Alacritty documents
|
|
70
|
+
no such variable, so they never pointed at anything Alacritty read. Use
|
|
71
|
+
`--config`.
|
|
72
|
+
- Alacritty configs are now also looked for at
|
|
73
|
+
`$XDG_CONFIG_HOME/alacritty.toml`, which is the second path Alacritty's own
|
|
74
|
+
search order checks. The legacy `alacritty.yml` still counts.
|
|
75
|
+
|
|
76
|
+
Nothing above changes what huebox writes to a config: colours only, line by
|
|
77
|
+
line — and a save that changes nothing no longer even touches the file, so its
|
|
78
|
+
mtime survives too.
|
|
79
|
+
|
|
80
|
+
## Themes
|
|
81
|
+
|
|
82
|
+
`~/.config/huebox` is the truth: one file per theme in `themes/<name>.toml`,
|
|
83
|
+
plus `state.toml` naming the one you are working on. A terminal config is a
|
|
84
|
+
push target, not the place your theme lives.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
huebox import dusk # snapshot this terminal into a theme
|
|
88
|
+
huebox edit dusk # edit it; every Ctrl+S saves and pushes
|
|
89
|
+
huebox use dusk # switch to it and push, no editor
|
|
90
|
+
huebox use dusk --to ghostty,kitty # push to specific terminals
|
|
91
|
+
huebox use dusk --no-push # switch without touching a config
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`huebox edit` with no name opens the current theme; `huebox show <name>` and
|
|
95
|
+
`huebox --dump <name>` read a theme file instead of a config. With no theme
|
|
96
|
+
library at all, `huebox edit` is what it always was: editing the terminal
|
|
97
|
+
config in place.
|
|
98
|
+
|
|
99
|
+
## Editing
|
|
100
|
+
|
|
101
|
+
Run `huebox edit` and drive it with the keyboard.
|
|
102
|
+
|
|
103
|
+
| Key | Action |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| arrows | move between slots — along a row, or up/down a row, in the grid on screen |
|
|
106
|
+
| `q` / `w` | hue −/+ |
|
|
107
|
+
| `a` / `s` | saturation −/+ |
|
|
108
|
+
| `z` / `x` | lightness −/+ |
|
|
109
|
+
| `f` | cycle step size ×1 → ×5 → ×20 |
|
|
110
|
+
| `i` | type a hex value |
|
|
111
|
+
| `Ctrl+S` | save — the session's only write: the theme file, then a push |
|
|
112
|
+
| `u` / `r` | undo / revert to the last save |
|
|
113
|
+
| `t` | theme picker — arrows, `Enter` opens, `n` makes a theme from the buffer, `Esc` back |
|
|
114
|
+
| `N` | save the buffer as a new theme (and then save it) |
|
|
115
|
+
| `Esc` | quit — twice if there are unsaved changes |
|
|
116
|
+
|
|
117
|
+
The mouse works too: click a swatch or interface cell to select it, click a
|
|
118
|
+
picker row to open it, wheel through a long picker list, and drag across the
|
|
119
|
+
code sample or diff to select text for copying. Clicking anywhere else — the
|
|
120
|
+
header, the hints, the empty air — does nothing, and the wheel does nothing
|
|
121
|
+
outside the picker.
|
|
122
|
+
|
|
123
|
+
Edits live in an in-memory buffer: nothing is written until you press
|
|
124
|
+
`Ctrl+S`. The editor *renders* from that buffer, so everything on screen —
|
|
125
|
+
palette, interface, code sample, and the background / selection / cursor
|
|
126
|
+
examples — is live and truecolor before the file changes (a frame with room to
|
|
127
|
+
spare also shows a git diff of the sample). The frame's own text is drawn
|
|
128
|
+
from the buffer too: the `huebox` wordmark at the top wears the six bright
|
|
129
|
+
hues one letter each, a key in the hint line is bright (`arrows`), the label
|
|
130
|
+
beside it is teal (**move**), the furniture — a path, a hex, a count — is
|
|
131
|
+
muted, and a section header is the theme's own foreground in bold. Edit
|
|
132
|
+
`palette-11` and both the escape in the sample and the keys change colour on
|
|
133
|
+
the same frame. The floor is the buffer's too: every row of the editor —
|
|
134
|
+
the ground under the palette grid, the air between the widgets, the column
|
|
135
|
+
after the last hint — is painted in `background`, so the frame is a sample
|
|
136
|
+
of the theme rather than a preview beside it.
|
|
137
|
+
|
|
138
|
+
The `selected` row ends in a reading of the slot's own colour: for each axis a
|
|
139
|
+
number and a bar — `hue 120° [the whole wheel] sat 48% [grey → colour] val
|
|
140
|
+
48% [black → colour]`. Each reading sits in a fixed-width field, so the bars
|
|
141
|
+
start in the same columns whatever the slot says and the row does not jump when
|
|
142
|
+
a number grows a digit. The bar is a sweep of its whole axis at the slot's own
|
|
143
|
+
other two readings — press `a`/`s` or `z`/`x` and the entire hue wheel repaints
|
|
144
|
+
— the number says what the reading is, and a hairline one eighth of a cell
|
|
145
|
+
wide, set into the cell the reading falls nearest, says where it sits on the
|
|
146
|
+
bar. The exact reading — `hue 120.0 sat 48.4% val 47.8%` — sits on the row
|
|
147
|
+
below beside the specimen: the bars and the numbers are the glance, the exact
|
|
148
|
+
numbers are the truth, and neither gives up a row for the other.
|
|
149
|
+
|
|
150
|
+
Quit with unsaved changes and huebox asks for a second `Esc`
|
|
151
|
+
first; `r` throws the
|
|
152
|
+
buffer away and goes back to your last save.
|
|
153
|
+
|
|
154
|
+
The header names what you are editing: `ember ● ghostty` for a theme (the `●`
|
|
155
|
+
marks unsaved buffer changes) or `direct:/path/to/config` in a legacy
|
|
156
|
+
direct-config session. `t` opens the theme picker without leaving the editor:
|
|
157
|
+
arrows and `Enter` to use a theme, `n` to make one from the buffer you are
|
|
158
|
+
looking at, `Esc` to go back. `Enter` is a save, not a peek — it writes the
|
|
159
|
+
theme, pushes it and reloads your terminal, so the theme you pick is the one
|
|
160
|
+
on screen; `Ctrl+S` stays for the buffer's own edits. Opening a theme while
|
|
161
|
+
the buffer has unsaved edits is refused with
|
|
162
|
+
`save (Ctrl+S) or revert (r) first` rather than losing them. `N` is the way
|
|
163
|
+
out of a direct-config session: it asks for a name, makes the theme, makes it
|
|
164
|
+
current, and saves it through the same pipeline. If a name is already taken,
|
|
165
|
+
huebox says so and waits for `y` (overwrite) or another name — no modal, and
|
|
166
|
+
nothing is written until you answer.
|
|
167
|
+
|
|
168
|
+
`Ctrl+S` is two writes: the theme file first, then a push into the terminal
|
|
169
|
+
config that holds your colours — the status bar says which
|
|
170
|
+
(`saved dusk → ghostty`). The theme file is the truth and is never rolled
|
|
171
|
+
back: if a push fails, the save still stands, huebox says why on stderr, and
|
|
172
|
+
the session ends with exit 1. Editing a terminal config directly (no themes
|
|
173
|
+
yet) takes a `<config>.huebox.bak` on its first save; theme files get none.
|
|
174
|
+
|
|
175
|
+
A push that lands asks the terminal to re-read its config, so the new colours
|
|
176
|
+
are live when the command finishes: ghostty is signalled the way `Ctrl+Shift+,`
|
|
177
|
+
signals it and kitty is asked over its own remote control. A terminal that
|
|
178
|
+
cannot be told keeps the advice line in the report — `Ctrl+Shift+,` for
|
|
179
|
+
ghostty, `Ctrl+Shift+F5` for kitty, and alacritty picks changes up by itself.
|
|
180
|
+
`--no-reload` turns the whole step off. The code sample inside the editor is
|
|
181
|
+
rendered in truecolor from the values you are editing, so it updates *before*
|
|
182
|
+
the reload. A tall enough frame also draws a
|
|
183
|
+
git diff of that sample — `+` lines wear palette 2, `-` lines palette 1 — so a
|
|
184
|
+
theme whose red and green are wrong says so before you reload; the hunk is
|
|
185
|
+
drawn only out of rows the sample did not need, so it never costs it a line.
|
|
186
|
+
|
|
187
|
+
A config is only ever edited line by line, so a colour the config does not
|
|
188
|
+
define is reported (`not carried by this config: cursor-text, …`) and left
|
|
189
|
+
alone — huebox will not invent a line in your terminal's config. A Ghostty
|
|
190
|
+
theme file is the one file huebox writes whole, and only because it is
|
|
191
|
+
named after the theme it holds.
|
|
192
|
+
|
|
193
|
+
### Ghostty themes
|
|
194
|
+
|
|
195
|
+
Ghostty keeps its colours in theme files, and a save joins it there whenever
|
|
196
|
+
your config is already organised that way — when it has a `theme =` line,
|
|
197
|
+
huebox writes the theme under *its own* name and repoints your config at
|
|
198
|
+
it:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
huebox use dusk --to ghostty # or edit dusk + Ctrl+S
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
That writes `~/.config/ghostty/themes/dusk` — 22 colours, in Ghostty's own
|
|
205
|
+
`palette = 0=#…` spelling, read back by huebox without drift — and points
|
|
206
|
+
your main config at it with a single `theme =` line: an existing one keeps
|
|
207
|
+
its spacing, its quotes and its comment and only the value changes, a config
|
|
208
|
+
without one gets the line appended, and every other byte of the file is left
|
|
209
|
+
exactly as it was. **Your previous theme file is not touched.** A theme's
|
|
210
|
+
colours never land in a file that belongs to another theme, which is the
|
|
211
|
+
whole reason the save goes through a file at all: saving `dusk` while your
|
|
212
|
+
config is on `Nightspice` gives you a `dusk` file and moves one line, where
|
|
213
|
+
before it would have rewritten `Nightspice` under a name that was no longer
|
|
214
|
+
true. The themes directory is shared with Ghostty's built-ins, so an export
|
|
215
|
+
overwrites a same-name file there (the report tells you when it did).
|
|
216
|
+
|
|
217
|
+
If your config holds its colours inline — or in a `config-file` include —
|
|
218
|
+
there is no theme name in play, so huebox edits the file the colours are
|
|
219
|
+
already in and leaves your layout alone. Two flags say it out loud:
|
|
220
|
+
`--ghostty-native` forces the export even there (adding the `theme =` line
|
|
221
|
+
for you), `--ghostty-in-place` forces the edit; asking for both is refused.
|
|
222
|
+
The forced export is the one case that can leave a colour behind a theme
|
|
223
|
+
file, and the report says so when it does. Both apply to the ghostty target
|
|
224
|
+
only — `--to ghostty,kitty` exports for Ghostty and pushes kitty the
|
|
225
|
+
ordinary way — and `--no-push` wins over either.
|
|
226
|
+
|
|
227
|
+
A `theme =` line pointing at a file that is not there (Ghostty calls that
|
|
228
|
+
a configuration error on reload) is treated as a broken config, not a
|
|
229
|
+
colourless one: `huebox use <name>` writes the file, fixes the pointer, and
|
|
230
|
+
tells you the file was missing. Nothing is overwritten to do it — the file
|
|
231
|
+
did not exist.
|
|
232
|
+
|
|
233
|
+
## Supported terminals
|
|
234
|
+
|
|
235
|
+
| Format | Config |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| Ghostty | `$XDG_CONFIG_HOME/ghostty/config.ghostty`, including `config-file` includes and `theme = Name` indirection |
|
|
238
|
+
| kitty | `$XDG_CONFIG_HOME/kitty/kitty.conf`, `~/.kitty.conf`, or `KITTY_CONFIG_DIRECTORY` |
|
|
239
|
+
| Alacritty | `$XDG_CONFIG_HOME/alacritty/alacritty.toml`, `$XDG_CONFIG_HOME/alacritty.toml`, `~/.alacritty.toml` (and the legacy `alacritty.yml`), dotted keys, `[section]` tables and inline tables |
|
|
240
|
+
|
|
241
|
+
The search order is each terminal's own (upstream docs, checked October
|
|
242
|
+
2026). `~/.config/...` paths follow `XDG_CONFIG_HOME` when you set it. Alacritty
|
|
243
|
+
documents no environment variable for its config path, so `--config` is the
|
|
244
|
+
only override there; for kitty, `KITTY_CONFIG_DIRECTORY` is upstream's
|
|
245
|
+
spelling (the old `KITTY_CONFIG_DIR` still works, deprecated, for one
|
|
246
|
+
release).
|
|
247
|
+
|
|
248
|
+
huebox only offers a terminal whose config actually contains colours, so a
|
|
249
|
+
leftover `ALACRITTY_SOCKET` from a session last week will not hijack your
|
|
250
|
+
Ghostty config — and it will not push into one either. Override the guess with
|
|
251
|
+
`--format`:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
huebox edit --format kitty
|
|
255
|
+
huebox show --config ~/dotfiles/alacritty.toml
|
|
256
|
+
huebox use ember --to ghostty --config ~/dotfiles/ghostty/config
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`--config` pushes one format only: with a single `--to` it names the file,
|
|
260
|
+
with several `--to` targets it is refused (ambiguity, not a guess).
|
|
261
|
+
|
|
262
|
+
## Safety
|
|
263
|
+
|
|
264
|
+
Writes are line-level: only the colour tokens are replaced, so comments,
|
|
265
|
+
ordering, alignment and every unrelated setting survive untouched. A save that
|
|
266
|
+
changes nothing is not just byte-identical, it does not write the file at all —
|
|
267
|
+
its mtime is untouched too, so a backup job or a config manager never notices a
|
|
268
|
+
save that saved nothing. Pushing a theme uses that same writer, so a push is no
|
|
269
|
+
more invasive than the v1 in-place edit. The only file huebox writes outside
|
|
270
|
+
your terminal config is its own `~/.config/huebox` library.
|
|
271
|
+
|
|
272
|
+
## Development
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
python3 -m unittest discover -s tests
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The tests cover reading every slot, round-tripping without drift, changing
|
|
279
|
+
only the intended lines, leaving a config untouched (bytes *and* mtime) when
|
|
280
|
+
nothing changed, following Ghostty includes and `theme =` pointers, pushing
|
|
281
|
+
without inventing keys, and keeping the layout inside narrow terminals.
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""huebox - a terminal theme editor with live preview.
|
|
2
|
+
|
|
3
|
+
Package split of the original single-file module (§17 of
|
|
4
|
+
docs/001-spec/spec.md). The v1 public names are re-exported here so
|
|
5
|
+
`import huebox` keeps working for existing tests and callers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# NOTE: version first — cli imports it during package initialisation.
|
|
9
|
+
__version__ = "0.3.0"
|
|
10
|
+
|
|
11
|
+
from .cli import main
|
|
12
|
+
from .color import MISSING, NAMED, PALETTE, SLOTS
|
|
13
|
+
from .formats import FORMAT_NAMES, FORMATS
|
|
14
|
+
from .render import (clip, diff_lines, example_lines, pack, render_preview,
|
|
15
|
+
sample_lines)
|
|
16
|
+
from .tui import term_size
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"__version__",
|
|
20
|
+
"main",
|
|
21
|
+
"PALETTE",
|
|
22
|
+
"NAMED",
|
|
23
|
+
"SLOTS",
|
|
24
|
+
"MISSING",
|
|
25
|
+
"FORMATS",
|
|
26
|
+
"FORMAT_NAMES",
|
|
27
|
+
"clip",
|
|
28
|
+
"diff_lines",
|
|
29
|
+
"pack",
|
|
30
|
+
"example_lines",
|
|
31
|
+
"render_preview",
|
|
32
|
+
"sample_lines",
|
|
33
|
+
"term_size",
|
|
34
|
+
]
|