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 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
+ ]
@@ -0,0 +1,7 @@
1
+ """`python -m huebox` (§17)."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ sys.exit(main())