Compare commits

..

6 Commits

Author SHA1 Message Date
hl-valdemar 437356272e fix: warm Pumpkin Light terminal chrome accents 2026-10-23 16:24:43 +02:00
hl-valdemar 2b0cee20ba feat: bundle Pumpkin Light and paired appearance switching 2026-10-23 11:40:58 +02:00
hl-valdemar f414bf0170 fix: strengthen Pumpkin syntax color separation 2026-10-23 11:36:08 +02:00
hl-valdemar 7cb4ce2902 fix: warm Pumpkin function and keyword colors 2026-10-23 11:24:57 +02:00
hl-valdemar 4136aa53c8 refactor: rename the bundled theme to Pumpkin 2026-10-23 11:24:18 +02:00
hl-valdemar 08135a407b feat: bundle Terebi Sunset as a selectable local theme 2026-10-23 11:24:18 +02:00
9 changed files with 204 additions and 12 deletions
+12 -1
View File
@@ -11,7 +11,7 @@ DARK_NOTIFY_LOG := $(HOME)/Library/Logs/dark-notify.log
CONFIG_MARKER := $(CONFIG_DIR)/.tint-installed-config CONFIG_MARKER := $(CONFIG_DIR)/.tint-installed-config
STATE_MARKER := $(CONFIG_DIR)/.tint-installed-state STATE_MARKER := $(CONFIG_DIR)/.tint-installed-state
.PHONY: install uninstall .PHONY: install install-schemes uninstall
install: default-state.toml config.toml install: default-state.toml config.toml
@command -v tinty >/dev/null || { echo "tinty is required" >&2; exit 1; } @command -v tinty >/dev/null || { echo "tinty is required" >&2; exit 1; }
@command -v dark-notify >/dev/null || { echo "dark-notify is required" >&2; exit 1; } @command -v dark-notify >/dev/null || { echo "dark-notify is required" >&2; exit 1; }
@@ -31,6 +31,7 @@ install: default-state.toml config.toml
touch "$(HERDR_CONFIG)" "$(HERDR_MARKER)"; \ touch "$(HERDR_CONFIG)" "$(HERDR_MARKER)"; \
fi fi
XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty sync XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty sync
$(MAKE) install-schemes
@dark_notify="$$(command -v dark-notify)"; \ @dark_notify="$$(command -v dark-notify)"; \
tinty_dir="$$(dirname "$$(command -v tinty)")"; \ tinty_dir="$$(dirname "$$(command -v tinty)")"; \
printf '%s\n' \ printf '%s\n' \
@@ -69,9 +70,19 @@ install: default-state.toml config.toml
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true @launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
launchctl bootstrap "gui/$$(id -u)" "$(LAUNCH_AGENT)" launchctl bootstrap "gui/$$(id -u)" "$(LAUNCH_AGENT)"
install-schemes: schemes/base16/pumpkin.yaml schemes/base16/pumpkin-light.yaml
@set -e; \
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)"; \
mkdir -p "$$data_dir/custom-schemes/base16"; \
cp schemes/base16/pumpkin.yaml schemes/base16/pumpkin-light.yaml "$$data_dir/custom-schemes/base16/"
uninstall: uninstall:
@launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true @launchctl bootout "gui/$$(id -u)/$(LAUNCH_AGENT_LABEL)" 2>/dev/null || true
rm -f "$(BIN_DIR)/tint" "$(LAUNCH_AGENT)" "$(DARK_NOTIFY_LOG)" rm -f "$(BIN_DIR)/tint" "$(LAUNCH_AGENT)" "$(DARK_NOTIFY_LOG)"
@if command -v tinty >/dev/null; then \
data_dir="$$(XDG_CONFIG_HOME="$(XDG_CONFIG_HOME)" tinty config --data-dir-path)" && \
rm -f "$$data_dir/custom-schemes/base16/pumpkin.yaml" "$$data_dir/custom-schemes/base16/pumpkin-light.yaml"; \
fi
@if [ -e "$(STATE_MARKER)" ]; then \ @if [ -e "$(STATE_MARKER)" ]; then \
rm -f "$(CONFIG_DIR)/state.toml" "$(STATE_MARKER)"; \ rm -f "$(CONFIG_DIR)/state.toml" "$(STATE_MARKER)"; \
fi fi
+19
View File
@@ -38,6 +38,7 @@ The installer:
- Copies the versioned `default-state.toml` to the installed `state.toml` only when no state file already exists. - Copies the versioned `default-state.toml` to the installed `state.toml` only when no state file already exists.
- Creates `~/.config/herdr/config.toml` if needed. - Creates `~/.config/herdr/config.toml` if needed.
- Runs `tinty sync` to download the configured theme repositories. - Runs `tinty sync` to download the configured theme repositories.
- Installs bundled custom schemes into Tinty's data directory.
- Creates and loads a `dark-notify` LaunchAgent for automatic macOS appearance switching. - Creates and loads a `dark-notify` LaunchAgent for automatic macOS appearance switching.
Add the binary directory to your shell path: Add the binary directory to your shell path:
@@ -172,6 +173,24 @@ TINTY_PAIR_APPEARANCE=light tint swamp
TINTY_PAIR_APPEARANCE=dark tint swamp TINTY_PAIR_APPEARANCE=dark tint swamp
``` ```
### Pumpkin
The bundled [`base16-pumpkin`](schemes/base16/pumpkin.yaml) and [`base16-pumpkin-light`](schemes/base16/pumpkin-light.yaml) palettes come from the sibling Terebi project. Dark Pumpkin uses brown, cream and vivid autumn accents; Pumpkin Light uses parchment, brown ink and deeper orange, persimmon, toasted amber, caramel and olive accents. Its ANSI cyan slot uses caramel so inverted terminal bars stay warm rather than olive. All light text/accent slots exceed 4.5:1 contrast against the background.
`make install` installs both palettes automatically. To install the palettes and updated wrapper without syncing repositories or reloading the appearance agent, then save and select their named pair:
```sh
make install-schemes
go build -o "$HOME/.local/bin/tint" .
tint pair pumpkin dark:base16-pumpkin light:base16-pumpkin-light
tint favorite pumpkin
tint pumpkin
```
`tint pumpkin` sets both sides and applies the side matching macOS appearance. Use `tint light` or `tint dark` to apply a saved side immediately. Saving/favoriting the pair without selecting it leaves the current pair and active theme unchanged.
Tinty stores both YAML files under `$(tinty config --data-dir-path)/custom-schemes/base16/` and builds the installed app templates when applying either theme. Both theme validation and app palette lookup support [Tinty's native custom schemes](https://github.com/tinted-theming/tinty/blob/v0.34.0/USAGE.md#use-your-own-schemes), so Ghostty, Zed, Herdr, OMP and Conjure use the normal application flow. List local palettes with `tinty list --custom-schemes`. Reinstalling updates the bundled palettes without changing saved state; fresh installs include the favorite `pumpkin` pair, and uninstalling removes both bundled YAML files.
## Update ## Update
After changing or updating the wrapper source: After changing or updating the wrapper source:
+13 -11
View File
@@ -51,18 +51,20 @@ func currentScheme() (tintyScheme, error) {
if err != nil { if err != nil {
return tintyScheme{}, err return tintyScheme{}, err
} }
output, err := exec.Command("tinty", "list", "--json").Output()
if err != nil {
return tintyScheme{}, err
}
var schemes []tintyScheme
if err := json.Unmarshal(output, &schemes); err != nil {
return tintyScheme{}, err
}
id := strings.TrimSpace(string(current)) id := strings.TrimSpace(string(current))
for _, scheme := range schemes { for _, args := range [][]string{{"list", "--json"}, {"list", "--json", "--custom-schemes"}} {
if scheme.ID == id { output, err := exec.Command("tinty", args...).Output()
return scheme, nil if err != nil {
return tintyScheme{}, err
}
var schemes []tintyScheme
if err := json.Unmarshal(output, &schemes); err != nil {
return tintyScheme{}, err
}
for _, scheme := range schemes {
if scheme.ID == id {
return scheme, nil
}
} }
} }
return tintyScheme{}, fmt.Errorf("scheme not found: %s", id) return tintyScheme{}, fmt.Errorf("scheme not found: %s", id)
+98
View File
@@ -0,0 +1,98 @@
package main
import (
"bytes"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestCustomSchemeCanBeFavoritedAndAppliedToApps(t *testing.T) {
tinty, err := exec.LookPath("tinty")
if err != nil {
t.Skip("requires tinty 0.34+")
}
home := t.TempDir()
data := filepath.Join(home, "data")
bin := filepath.Join(home, "bin")
for _, dir := range []string{bin, filepath.Join(data, "repos", "schemes", "base16"), filepath.Join(data, "custom-schemes", "base16"), filepath.Join(home, ".config", "herdr")} {
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
}
palette, err := os.ReadFile("schemes/base16/pumpkin.yaml")
if err != nil {
t.Fatal(err)
}
files := map[string][]byte{
filepath.Join(data, "custom-schemes", "base16", "pumpkin.yaml"): palette,
filepath.Join(home, "config.toml"): []byte("items = []\n"),
filepath.Join(home, ".config", "herdr", "config.toml"): []byte("[server]\nport = 8080\n"),
filepath.Join(home, "zed.json"): []byte(`{"name":"Pumpkin","themes":[{"name":"Pumpkin","appearance":"dark"}]}`),
}
for path, contents := range files {
if err := os.WriteFile(path, contents, 0o644); err != nil {
t.Fatal(err)
}
}
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
exec "$TINTY_TEST_BINARY" --data-dir "$TINTY_TEST_DATA" --config "$TINTY_TEST_CONFIG" "$@"
`)
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("PATH", bin+":/usr/bin:/bin")
t.Setenv("TINTY_TEST_BINARY", tinty)
t.Setenv("TINTY_TEST_DATA", data)
t.Setenv("TINTY_TEST_CONFIG", filepath.Join(home, "config.toml"))
statePath := filepath.Join(home, "state.toml")
t.Setenv("TINT_STATE_FILE", statePath)
initial := state{Current: currentPair{Light: "personal-light", Dark: "personal-dark"}}
if err := saveState(statePath, initial); err != nil {
t.Fatal(err)
}
var stdout, stderr bytes.Buffer
if err := runCLI([]string{"favorite", "base16-pumpkin"}, &stdout, &stderr); err != nil {
t.Fatalf("custom scheme could not be favorited: %v: %s", err, stderr.String())
}
value, err := loadState(statePath)
if err != nil {
t.Fatal(err)
}
if value.Current != initial.Current || len(value.Catalog.FavoriteThemes) != 1 || value.Catalog.FavoriteThemes[0] != "base16-pumpkin" {
t.Fatalf("favorite changed the current pair or omitted the scheme: %#v", value)
}
before := readFile(t, statePath)
if err := runCLI([]string{"favorite", "base16-missing"}, &stdout, &stderr); err == nil {
t.Fatal("accepted a nonexistent custom scheme")
}
if readFile(t, statePath) != before {
t.Fatal("invalid scheme changed saved state")
}
if output, err := exec.Command("tinty", "apply", "base16-pumpkin").CombinedOutput(); err != nil {
t.Fatalf("Tinty could not apply the custom scheme: %v\n%s", err, output)
}
if err := applyApps(filepath.Join(home, "zed.json")); err != nil {
t.Fatalf("custom scheme could not be applied: %v", err)
}
var omp struct {
Colors map[string]string `json:"colors"`
}
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".omp", "agent", "themes", "tinty-current.json"))), &omp); err != nil {
t.Fatal(err)
}
if omp.Colors["text"] != "#ffe3b5" || omp.Colors["syntaxNumber"] != "#f2943b" {
t.Fatalf("OMP did not receive the custom scheme: %#v", omp.Colors)
}
var conjure struct {
Variant string `json:"variant"`
Colors map[string]string `json:"colors"`
}
if err := json.Unmarshal([]byte(readFile(t, filepath.Join(home, ".config", "conjure", "theme.json"))), &conjure); err != nil {
t.Fatal(err)
}
if conjure.Variant != "dark" || conjure.Colors["background"] != "#1f1714" || conjure.Colors["secondaryText"] != "#d9c7a6" {
t.Fatalf("Conjure did not receive the custom scheme: %#v", conjure)
}
}
+5
View File
@@ -12,6 +12,11 @@ name = "gruvbox-pale"
dark = "base16-gruvbox-dark-pale" dark = "base16-gruvbox-dark-pale"
light = "base16-gruvbox-light-soft" light = "base16-gruvbox-light-soft"
[[favorites.pairs]]
name = "pumpkin"
dark = "base16-pumpkin"
light = "base16-pumpkin-light"
[[favorites.pairs]] [[favorites.pairs]]
name = "swamp" name = "swamp"
dark = "base16-swamp-dark" dark = "base16-swamp-dark"
+5
View File
@@ -34,8 +34,12 @@ func TestInstallFromFreshCheckout(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
if err := os.CopyFS(filepath.Join(source, "schemes"), os.DirFS("schemes")); err != nil {
t.Fatal(err)
}
writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh writeExecutable(t, filepath.Join(bin, "tinty"), `#!/bin/sh
case "$1" in case "$1" in
config) printf '%s/tinted-theming/tinty\n' "$XDG_DATA_HOME" ;;
sync|info) exit 0 ;; sync|info) exit 0 ;;
apply) printf '%s\n' "$2" >> "$TINTY_LOG" ;; apply) printf '%s\n' "$2" >> "$TINTY_LOG" ;;
*) exit 2 ;; *) exit 2 ;;
@@ -54,6 +58,7 @@ esac
t.Setenv("GOMODCACHE", strings.TrimSpace(string(cache))) t.Setenv("GOMODCACHE", strings.TrimSpace(string(cache)))
t.Setenv("PATH", bin+":"+os.Getenv("PATH")) t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
t.Setenv("TINTY_LOG", log) t.Setenv("TINTY_LOG", log)
t.Setenv("XDG_DATA_HOME", filepath.Join(tmp, "data"))
install := func() { install := func() {
t.Helper() t.Helper()
cmd := exec.Command("make", "install", "HOME="+home, "XDG_CONFIG_HOME="+config) cmd := exec.Command("make", "install", "HOME="+home, "XDG_CONFIG_HOME="+config)
+5
View File
@@ -192,6 +192,11 @@ func validatePair(pair currentPair, stderr io.Writer) error {
func validateTheme(theme string, stderr io.Writer) error { func validateTheme(theme string, stderr io.Writer) error {
cmd := exec.Command("tinty", "info", theme) cmd := exec.Command("tinty", "info", theme)
cmd.Stdout = io.Discard cmd.Stdout = io.Discard
if err := cmd.Run(); err == nil {
return nil
}
cmd = exec.Command("tinty", "info", theme, "--custom-schemes")
cmd.Stdout = io.Discard
cmd.Stderr = stderr cmd.Stderr = stderr
return cmd.Run() return cmd.Run()
} }
+23
View File
@@ -0,0 +1,23 @@
# Matching light companion to Pumpkin; deeper accents for parchment backgrounds.
# Styling roles: https://github.com/tinted-theming/home/blob/main/styling.md
system: "base16"
name: "Pumpkin Light"
author: "Terebi"
variant: "light"
palette:
base00: "#fff6e6" # Parchment background
base01: "#f4e6ce" # Raised surface
base02: "#e6ceaa" # Selection
base03: "#7c624e" # Comments / muted text
base04: "#70543f" # Secondary foreground
base05: "#453126" # Brown foreground
base06: "#30221b" # Deep foreground
base07: "#1f1714" # Darkest foreground
base08: "#b13425" # Red — variables / errors / deletions
base09: "#a64b00" # Burnt orange — numbers / constants
base0A: "#8a621c" # Toasted amber — types / search
base0B: "#52651f" # Olive — strings / additions
base0C: "#995b2b" # Caramel — escapes / support / inverted terminal bars
base0D: "#b34d00" # Pumpkin orange — functions / headings / UI accent
base0E: "#b63d24" # Persimmon — keywords
base0F: "#c2452e" # Ember — deprecated
+24
View File
@@ -0,0 +1,24 @@
# Palette source: terebi/Themes/pumpkin.yaml (sibling project).
# App colors are rounded to 8-bit sRGB; the translucent backdrop becomes opaque.
# Styling roles: https://github.com/tinted-theming/home/blob/main/styling.md
system: "base16"
name: "Pumpkin"
author: "Terebi"
variant: "dark"
palette:
base00: "#1f1714" # Background — app backdrop, without alpha
base01: "#2c221c" # Raised background / status bars
base02: "#45352b" # Selection
base03: "#9b8775" # Comments / muted text
base04: "#d9c7a6" # Secondary foreground — app secondaryText
base05: "#ffe3b5" # Foreground — app text
base06: "#ffedcf" # Light foreground
base07: "#fff6e6" # Lightest foreground
base08: "#e07864" # Red — variables / errors / deletions
base09: "#f2943b" # Orange — numbers / constants; app accent
base0A: "#e6bc65" # Yellow — types / search
base0B: "#a9b665" # Green — strings / additions
base0C: "#d6c56b" # Harvest gold — escapes / support
base0D: "#ff9e3b" # Pumpkin orange — functions / headings / UI accent
base0E: "#ef7956" # Persimmon — keywords
base0F: "#c2452e" # Brown/red — deprecated; app ember (3.51:1 on base00)