[Sync] Lua Hyprland chain, licence cleanup
This commit is contained in:
1 parent
93b4f695ce
commit
47166d877f
183 files changed
+5614
-5835
No files matched your search
@@ -36,3 +36,11 @@ __pycache__/
|
|||||||
# HyDE generates waybar's include list per machine, with ABSOLUTE $HOME paths — the committed copy
|
# HyDE generates waybar's include list per machine, with ABSOLUTE $HOME paths — the committed copy
|
||||||
# carried fl's /home/anon into every gui host, where it resolved to nothing. Let each box write it.
|
# carried fl's /home/anon into every gui host, where it resolved to nothing. Let each box write it.
|
||||||
**/waybar/includes/includes.json
|
**/waybar/includes/includes.json
|
||||||
|
|
||||||
|
# dconf binary db — if stow ever folds ~/.config/dconf into a dir symlink on a fresh
|
||||||
|
# host, the dconf daemon writes the user db next to the tracked profile, into the repo.
|
||||||
|
gui/.config/dconf/hyde_hyprland
|
||||||
|
|
||||||
|
# llama.cpp router bench ledgers — generated next to bench.py, per-machine (fl/lw/wm)
|
||||||
|
**/llamacpp/bench-results.md
|
||||||
|
**/llamacpp/bench-history.md
|
||||||
@@ -13,7 +13,7 @@ common/ # shared everywhere (mirrors $HOME): fish, nvim, bat, btop, tmux, pi,
|
|||||||
gui/ # GUI configs shared by graphical hosts: hypr, waybar, kitty, alacritty, mpv, zellij
|
gui/ # GUI configs shared by graphical hosts: hypr, waybar, kitty, alacritty, mpv, zellij
|
||||||
wm/ # desktop: 4K monitors, NVIDIA, cloud LLM (duskadiy), work aliases
|
wm/ # desktop: 4K monitors, NVIDIA, cloud LLM (duskadiy), work aliases
|
||||||
lw/ # laptop: no NVIDIA, roaming VPNs, network/LLM tools
|
lw/ # laptop: no NVIDIA, roaming VPNs, network/LLM tools
|
||||||
fl/ # KDE Plasma box: a few Hyprland overrides + llamacpp, evremap
|
fl/ # KDE Plasma box (no Hyprland session): llamacpp, whisper, evremap
|
||||||
bin/ # tooling (not a stow package): install.sh companions
|
bin/ # tooling (not a stow package): install.sh companions
|
||||||
install.sh
|
install.sh
|
||||||
```
|
```
|
||||||
@@ -22,6 +22,8 @@ A **headless** (terminal-only) machine has no overlay package — it runs `commo
|
|||||||
headless with `touch ~/.config/dots-headless` (a machine-local, hostname-independent marker); it then
|
headless with `touch ~/.config/dots-headless` (a machine-local, hostname-independent marker); it then
|
||||||
skips `gui` too, so the same terminal-only setup replicates across any number of machines. In other
|
skips `gui` too, so the same terminal-only setup replicates across any number of machines. In other
|
||||||
words `common` *is* the headless baseline; `gui` and the host packages layer graphical machines on top.
|
words `common` *is* the headless baseline; `gui` and the host packages layer graphical machines on top.
|
||||||
|
Run it as a bare `./install.sh` — never `./install.sh common` (`common`/`gui` are layers, not hosts,
|
||||||
|
and install.sh refuses them).
|
||||||
|
|
||||||
### How it deploys
|
### How it deploys
|
||||||
|
|
||||||
@@ -68,9 +70,8 @@ cd ~/.dots
|
|||||||
`./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert),
|
`./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert),
|
||||||
`-h` help. GUI hosts also need **HyDE installed** first — see *HyDE dependency* below.
|
`-h` help. GUI hosts also need **HyDE installed** first — see *HyDE dependency* below.
|
||||||
|
|
||||||
Once per machine afterwards: `bat cache --build` (registers the repo's custom bat themes —
|
Once per machine afterwards: `:Lazy restore` inside nvim (`restore` obeys the shared
|
||||||
without it every `bat` run warns `Unknown theme 'onedark'`), and `:Lazy restore` inside nvim
|
`lazy-lock.json` pins; `sync`/`update` would rewrite them).
|
||||||
(`restore` obeys the shared `lazy-lock.json` pins; `sync`/`update` would rewrite them).
|
|
||||||
nvim's treesitter (`main` branch) builds parsers via the `tree-sitter` CLI — `pacman -S
|
nvim's treesitter (`main` branch) builds parsers via the `tree-sitter` CLI — `pacman -S
|
||||||
tree-sitter-cli` first, or `:TSInstallAll` fails with `ENOENT ... 'tree-sitter'`.
|
tree-sitter-cli` first, or `:TSInstallAll` fails with `ENOENT ... 'tree-sitter'`.
|
||||||
|
|
||||||
@@ -125,32 +126,40 @@ Because configs are symlinks into the repo, **you almost never run `install.sh`
|
|||||||
|
|
||||||
| Difference | Mechanism |
|
| Difference | Mechanism |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Monitors | `<h>/.config/hypr/monitors.conf` (sourced by the shared `hyprland.conf`) |
|
| Monitors | `<h>/.config/hypr/host.lua` (`hl.monitor` + workspace pins; loaded by the shared `hyprland.lua`) |
|
||||||
| NVIDIA | `wm/.config/hypr/nvidia.conf`, pulled in by wm's `host.conf` (fl/lw have no NVIDIA) |
|
| NVIDIA | the NVIDIA block in `wm/.config/hypr/host.lua` (lw has no NVIDIA) |
|
||||||
| Shell aliases | shared `config.fish` sources `~/.config/fish/host.fish` (per host) |
|
| Shell aliases | shared `config.fish` sources `~/.config/fish/host.fish` (per host) |
|
||||||
| Bash extras | shared `.bashrc` sources `~/.bashrc.host` (optional, per host) |
|
| Bash extras | shared `.bashrc` sources `~/.bashrc.host` (optional, per host) |
|
||||||
| fl's Hyprland | overrides ~9 hypr files (`hyprland.conf`, keybinds, windowrules, theme colors, monitors); inherits the rest from `gui` |
|
|
||||||
| Unique apps | in the owning host overlay (fl: llamacpp/evremap; lw: xremap) |
|
| Unique apps | in the owning host overlay (fl: llamacpp/evremap; lw: xremap) |
|
||||||
|
|
||||||
`aichat`, `pi`, and `opencode` share one config in `common` (LAN-first, with the cloud provider also
|
`aichat`, `pi`, and `opencode` share one config in `common` (LAN-first, with the cloud provider also
|
||||||
listed so any host can switch). `lw` carries a host overlay of all three that adds its local CPU
|
listed so any host can switch). `lw` and `wm` each carry a host overlay of all three that adds their
|
||||||
llama.cpp models — mirror edits between the common and `lw` copies. The opencode tree is documented
|
own llama.cpp models (lw: CPU; wm: the GTX 1080 Ti router, plus the duskadiy default) — mirror
|
||||||
|
edits between the common copy and both overlays. The opencode tree is documented
|
||||||
in `common/.config/opencode/README.md`.
|
in `common/.config/opencode/README.md`.
|
||||||
|
|
||||||
## HyDE dependency (important for GUI hosts)
|
## HyDE dependency (important for GUI hosts)
|
||||||
|
|
||||||
The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based. `gui/.config/hypr/*` is
|
The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based, since 2026-08-21 through
|
||||||
your customization layer, but it **sources HyDE's framework**:
|
HyDE 26.x's **Lua chain** (live on wm since then and on lw since 2026-11; merged into `main`
|
||||||
`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables. So a GUI
|
2026-11-09 — see `LUA-PORT.md`). The repo pins `HYPRLAND_CONFIG` at
|
||||||
host must have **HyDE installed**, or Hyprland fails to start (`source globbing error` +
|
`~/.local/share/hypr/hyde.lua` in two files — `gui/.config/fish/conf.d/00-hypr-config.fish`
|
||||||
`invalid mod $mainMod`).
|
(SDDM/fish session path) and `gui/.config/uwsm/env-hyprland.d/10-dots.sh` (uwsm path) — and hyde.lua
|
||||||
|
loads HyDE's `lua/` modules, then the repo's override `gui/.config/hypr/hyprland.lua`, which pulls in
|
||||||
|
`dots_rules.lua` + `dots_binds.lua` (split out of it) and the per-host `host.lua`. So a GUI host must have **HyDE installed** — it ships `hyde.lua` and the
|
||||||
|
modules, both overwritten on HyDE updates; the dots layer lives only in `~/.config/hypr/*.lua`, which
|
||||||
|
survives them. `LUA-PORT.md` has the full map, flip recipe and rollback.
|
||||||
|
|
||||||
That file used to be generated-and-untracked, but **HyDE 26.x deploys neither it nor `hyde.conf`**,
|
The legacy hyprlang chain (`gui/.config/hypr/*.conf`, plus the two `~/.local/share/hyde/
|
||||||
so since the 2026-08-14 audit both are **tracked pins** in `gui/.local/share/hyde/` — the one
|
{hyprland,hyde}.conf` framework copies HyDE 26.x stopped shipping) was **retired 2026-09-10**
|
||||||
deliberate exception to the generated-files-are-never-tracked rule below, interim until the Lua port.
|
(`LUA-PORT.md` step 8) once the Lua soak closed — nothing reads a `.conf` in Lua mode. One line
|
||||||
After pulling that audit, a machine must run **`./bin/hyde-materialize.sh` before `./install.sh`**:
|
survives: `gui/.local/share/hyde/hyde.conf` keeps `$default.FONT`, the fallback HyDE's rofi/menu
|
||||||
the newly-untracked files leave dangling stow symlinks otherwise, and hypr/hyprlock treat a missing
|
scripts resolve FONT through (`get_hyprConf`) when the theme's `hypr.theme` names no font. Rolling
|
||||||
`source =` as a hard parse error.
|
back to hyprlang now means restoring those files from history (`git restore --source=bddbbf3 -- gui/.config/hypr wm/.config/hypr lw/.config/hypr gui/.local/share/hyde`), `./install.sh <host>`,
|
||||||
|
repointing the two pins and relogging; d851213 is the last hyprlang `main` (the port merged
|
||||||
|
2026-11-09). After pulling the 2026-08-14 audit, a machine must run
|
||||||
|
**`./bin/hyde-materialize.sh` before `./install.sh`**: the newly-untracked files leave dangling stow
|
||||||
|
symlinks otherwise, and hypr/hyprlock treat a missing `source =` as a hard parse error.
|
||||||
|
|
||||||
- Install/update HyDE with its own installer (`~/HyDE/Scripts/install.sh`); it also pulls the
|
- Install/update HyDE with its own installer (`~/HyDE/Scripts/install.sh`); it also pulls the
|
||||||
required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`).
|
required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`).
|
||||||
@@ -172,8 +181,8 @@ track neither. HyDE-managed customization goes via:
|
|||||||
**The custom cava rig is NOT HyDE-managed**: `layouts/custom.jsonc` + `layouts/custom.css` form a
|
**The custom cava rig is NOT HyDE-managed**: `layouts/custom.jsonc` + `layouts/custom.css` form a
|
||||||
self-contained two-bar config run by `wbar` (`gui/.local/bin/wbar`) as the supervised systemd
|
self-contained two-bar config run by `wbar` (`gui/.local/bin/wbar`) as the supervised systemd
|
||||||
user unit `dots-waybar.service` (`Restart=always` — waybar 0.15.0 dies on every DP-2 link
|
user unit `dots-waybar.service` (`Restart=always` — waybar 0.15.0 dies on every DP-2 link
|
||||||
drop/return, and as a bare process nothing relaunched it). The `exec-once` in `userprefs.conf`,
|
drop/return, and as a bare process nothing relaunched it). `hc.start.bar` in `hyprland.lua`, the
|
||||||
the Ctrl+Alt+W / Super+Shift+R binds and the `wbar` command all go through it; **never `killall
|
Ctrl+Alt+W / Super+Shift+R binds and the `wbar` command all go through it; **never `killall
|
||||||
waybar`** — systemd brings it back in seconds and a raw relaunch beside it means two bars
|
waybar`** — systemd brings it back in seconds and a raw relaunch beside it means two bars
|
||||||
(`wbar toggle|restart|reload|stop`). **Never route it through `hyde-shell waybar --set`** — waybar.py would clobber the repo's files as above. HyDE only reads
|
(`wbar toggle|restart|reload|stop`). **Never route it through `hyde-shell waybar --set`** — waybar.py would clobber the repo's files as above. HyDE only reads
|
||||||
from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar must be
|
from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar must be
|
||||||
@@ -181,10 +190,10 @@ disabled once, or **two bars appear at every login** — and the launcher differ
|
|||||||
|
|
||||||
- current HyDE: systemd user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service` (auto-restarting) —
|
- current HyDE: systemd user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service` (auto-restarting) —
|
||||||
`systemctl --user stop <unit>; systemctl --user mask <unit>`.
|
`systemctl --user stop <unit>; systemctl --user mask <unit>`.
|
||||||
- older (pre-unit) HyDE, e.g. wm's: `exec-once = $start.BAR` in HyDE's generated `hyprland.conf`
|
- older (pre-unit) HyDE: `exec-once = $start.BAR` in HyDE's generated `hyprland.conf` — the
|
||||||
— neutralized by `$start.BAR=` in `gui/.config/hypr/hyde.conf` (empty = unset; hyde.conf is
|
hyprlang-era dots blanked it with `$start.BAR=` in `gui/.config/hypr/hyde.conf`; that file went
|
||||||
sourced before that exec-once). Current HyDE's `variables.conf` re-sets `$start.BAR`, so that
|
with the chain (LUA-PORT.md step 8), and under Lua the lever is `hc.start.bar` in
|
||||||
line stops working after an update — see HYDE-UPDATE.md step 4.
|
`hyprland.lua`, which launches the rig itself — see HYDE-UPDATE.md step 4.
|
||||||
|
|
||||||
Note: the Arch `waybar` build has **no native `cava`** module — the rig uses the script module
|
Note: the Arch `waybar` build has **no native `cava`** module — the rig uses the script module
|
||||||
`custom/cava` running `cava-waybar.sh` (self-contained, no HyDE dependency).
|
`custom/cava` running `cava-waybar.sh` (self-contained, no HyDE dependency).
|
||||||
@@ -225,11 +234,31 @@ The repo is GPL-3.0 (`LICENSE`). What that does and doesn't cover:
|
|||||||
[HyDE](https://github.com/HyDE-Project/HyDE)** (and, further back, legacy Hyprdots). HyDE is
|
[HyDE](https://github.com/HyDE-Project/HyDE)** (and, further back, legacy Hyprdots). HyDE is
|
||||||
itself GPL-3.0, so the derivation and this repo's licence agree — no conflict, and the
|
itself GPL-3.0, so the derivation and this repo's licence agree — no conflict, and the
|
||||||
Hyprdots-era ASCII banners in those files are left in place as attribution.
|
Hyprdots-era ASCII banners in those files are left in place as attribution.
|
||||||
- **`common/.config/aichat/dark.tmTheme`** is *One Half Dark* by Son A. Pham, **MIT**. The
|
- **Colour themes**: the repo ships no third-party theme files, so it needs no notices file.
|
||||||
licence header at the top of that file is the attribution — don't strip it.
|
Each app gets its theme from somewhere else:
|
||||||
|
- **kitty**: HyDE renders `theme.conf` from the current HyDE theme. `kitty.conf` adds only
|
||||||
|
this repo's own background, foreground and tab bar.
|
||||||
|
- **aichat**: *One Half Dark* (MIT, by Son A. Pham) is downloaded from upstream at install
|
||||||
|
time by `bin/dots-themes-fetch.sh`, pinned by commit and checksum.
|
||||||
|
- **cava**: it writes its own shaders and themes.
|
||||||
|
- **zellij and bat**: their gruvbox-dark and OneHalfDark themes are built in.
|
||||||
|
|
||||||
|
The pi, opencode and alacritty themes are the repo's own files. They hold colour values from
|
||||||
|
the Catppuccin Mocha and SynthWave '84 palettes (both MIT, by Catppuccin and Robb Owen),
|
||||||
|
mapped onto each app's keys. Credit, gladly given: a set of colour values isn't a copy of
|
||||||
|
either licensed theme. Keep it that way: a theme file copied in brings its licence notice
|
||||||
|
with it.
|
||||||
|
- **`gui/.local/share/icons/dots-icons/`** ships only its `index.theme`. Its tray/status icons
|
||||||
|
are symlinks into each host's own *Tela-circle-dracula* (by vinceliuice, installed by HyDE),
|
||||||
|
made at install time by `bin/dots-icons-link.sh`. Tela-circle publishes no licence, so the
|
||||||
|
repo doesn't redistribute its SVGs.
|
||||||
- **`common/.config/fastfetch/pngs/`** holds project and distro marks (Arch, the Open Source
|
- **`common/.config/fastfetch/pngs/`** holds project and distro marks (Arch, the Open Source
|
||||||
Initiative and Open Source Hardware logos, …). Trademarks aren't relicensable, so GPL-3 does
|
Initiative and Open Source Hardware logos). Trademarks aren't relicensable, so GPL-3 does
|
||||||
not extend to them; they are bundled as nominative use, the way most dotfiles repos do.
|
not extend to them; they are bundled as nominative use, the way most dotfiles repos do. The
|
||||||
- **`gui/.config/hypr/hyprlock/assets/`** — the lock screen background and logo. EXIF is
|
dmz.rs rabbit (`dmz-rabbit*.png`) and the decentrala.org network logo (`network*.png`) are
|
||||||
stripped, so the files carry no provenance; confirm these are yours to redistribute before
|
the community's own marks, used with permission. `neonom.png` is the repo owner's own render
|
||||||
publishing, or swap them for something you know the origin of.
|
of the Om glyph, offered as **CC0**.
|
||||||
|
- **`gui/.config/hypr/hyprlock/assets/`** — the lock screen background and logo are AI-generated
|
||||||
|
by the repo owner (February 2026). They sit outside the GPL-3 grant and are offered as **CC0**.
|
||||||
|
The keyhole in `background.jpg` is an AI-altered rendition of the Open Source Initiative mark,
|
||||||
|
used purely decoratively; no affiliation with or endorsement by the OSI is implied.
|
||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dots-icons-link.sh — fill the dots-icons overlay theme with symlinks into the host's own
|
||||||
|
# Tela-circle-dracula install (HyDE ships that icon theme on every GUI host).
|
||||||
|
#
|
||||||
|
# The overlay (gui/.local/share/icons/dots-icons/index.theme, searched before Tela) exists
|
||||||
|
# because Tela ships its flat tray/status icons only at fixed 16/22/24px. On a fractionally
|
||||||
|
# scaled monitor waybar asks for size×2 (44px), which falls through to Tela's coloured
|
||||||
|
# scalable/devices circles (the blue nm-applet blob). Serving Tela's 22/panel SVGs as
|
||||||
|
# Scalable keeps them flat at every size.
|
||||||
|
#
|
||||||
|
# Links, not copies: Tela-circle publishes no licence (2026-11-09), so the repo can't
|
||||||
|
# redistribute its SVGs. Each host links its own installed files instead.
|
||||||
|
#
|
||||||
|
# Usage: bin/dots-icons-link.sh [-n] (-n = dry run). Idempotent; ./install.sh runs it on
|
||||||
|
# GUI hosts. Exit 1 if Tela is missing or any icon could not be linked — the overlay then
|
||||||
|
# degrades gracefully (Inherits=Tela-circle-dracula), so callers treat that as a warning.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DRY=0
|
||||||
|
[ "${1:-}" = "-n" ] && DRY=1
|
||||||
|
data="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||||
|
src="$data/icons/Tela-circle-dracula/22/panel"
|
||||||
|
dst="$data/icons/dots-icons/scalable/status"
|
||||||
|
|
||||||
|
icons=(
|
||||||
|
blueman-active blueman-disabled blueman-tray-active blueman-tray-disabled blueman-tray
|
||||||
|
nm-adhoc nm-device-wired-autoip nm-device-wired-secureoffline nm-device-wired-secure
|
||||||
|
nm-device-wired nm-device-wireless nm-device-wwan nm-mb-roam nm-nm-signal-00
|
||||||
|
nm-nm-signal-100 nm-nm-signal-25 nm-nm-signal-50 nm-nm-signal-75 nm-no-connection
|
||||||
|
nm-secure-lock nm-signal-00-secure nm-signal-00 nm-signal-0-secure nm-signal-0
|
||||||
|
nm-signal-100-secure nm-signal-100 nm-signal-25-secure nm-signal-25 nm-signal-50-secure
|
||||||
|
nm-signal-50 nm-signal-75-secure nm-signal-75 nm-tech-3g nm-tech-4g nm-tech-cdma-1x
|
||||||
|
nm-tech-edge nm-tech-evdo nm-tech-gprs nm-tech-hspa nm-tech-lte nm-tech-umts
|
||||||
|
nm-vpn-active-lock nm-vpn-lock nm-vpn-standalone-lock nm-wwan-tower
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ ! -d "$src" ]; then
|
||||||
|
echo " dots-icons: $src missing — Tela-circle-dracula not installed; overlay left empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[ "$DRY" = 1 ] || mkdir -p "$dst"
|
||||||
|
|
||||||
|
linked=0 ok=0 bad=0
|
||||||
|
for n in "${icons[@]}"; do
|
||||||
|
s="$src/$n.svg" d="$dst/$n.svg"
|
||||||
|
if [ ! -e "$s" ]; then
|
||||||
|
echo " dots-icons: $n.svg not in Tela-circle-dracula/22/panel (renamed upstream?)" >&2
|
||||||
|
bad=$((bad + 1))
|
||||||
|
elif [ -L "$d" ] && [ "$(readlink "$d")" = "$s" ]; then
|
||||||
|
ok=$((ok + 1))
|
||||||
|
elif [ -e "$d" ] && [ ! -L "$d" ]; then
|
||||||
|
echo " dots-icons: $d is a real file — left alone" >&2
|
||||||
|
bad=$((bad + 1))
|
||||||
|
else
|
||||||
|
[ "$DRY" = 1 ] || ln -sfn "$s" "$d"
|
||||||
|
linked=$((linked + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
verb="linked"
|
||||||
|
[ "$DRY" = 1 ] && verb="would link"
|
||||||
|
echo " dots-icons: $verb $linked, $ok already linked, $bad problem(s)"
|
||||||
|
[ "$bad" -eq 0 ]
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dots-themes-fetch.sh — download the third-party theme files this repo's configs use but the
|
||||||
|
# repo doesn't ship. Each host fetches its own copy from upstream, so the repo redistributes
|
||||||
|
# nothing and needs no licence notice. (Same idea as bin/dots-icons-link.sh, which links Tela's
|
||||||
|
# icons from the host instead of shipping them.)
|
||||||
|
#
|
||||||
|
# Every entry is pinned to an upstream commit plus a sha256, so a fetch is exactly the file
|
||||||
|
# that was reviewed. To update one: bump the commit in the URL, check the diff, then replace
|
||||||
|
# the checksum with `curl -fsSL <url> | sha256sum`.
|
||||||
|
#
|
||||||
|
# Usage: bin/dots-themes-fetch.sh [-n] (-n = dry run). Idempotent; ./install.sh runs it on
|
||||||
|
# every host. A destination that's already a real file is left alone, even if its content
|
||||||
|
# differs (a local edit wins). A symlink there (e.g. the stow link left dangling after the
|
||||||
|
# repo dropped the file) is replaced: mv swaps the link itself, never writes through it.
|
||||||
|
# Exit 1 if a download or checksum failed; the app then falls back to its built-in theme, so
|
||||||
|
# install.sh only warns.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DRY=0
|
||||||
|
[ "${1:-}" = "-n" ] && DRY=1
|
||||||
|
cfg="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||||
|
|
||||||
|
# dest|sha256|url
|
||||||
|
themes=(
|
||||||
|
# aichat: One Half Dark by Son A. Pham (MIT), loaded as <config>/dark.tmTheme when present
|
||||||
|
"$cfg/aichat/dark.tmTheme|6205e2311e433841f0aa1b15862b46cb892b003650f31df508a4ebc39a27bdbb|https://raw.githubusercontent.com/sonph/onehalf/d13f47b0c20923eb13335f7effc5e8ec4431a5cd/sublimetext/OneHalfDark.tmTheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
fetched=0 ok=0 kept=0 bad=0
|
||||||
|
for t in "${themes[@]}"; do
|
||||||
|
IFS='|' read -r dst sum url <<<"$t"
|
||||||
|
name="$dst"
|
||||||
|
case "$dst" in "$HOME"/*) name="~${dst#"$HOME"}" ;; esac
|
||||||
|
if [ ! -d "$(dirname "$dst")" ]; then
|
||||||
|
echo " themes: $name skipped (app not configured on this host)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -f "$dst" ] && [ ! -L "$dst" ]; then
|
||||||
|
if [ "$(sha256sum <"$dst" | cut -d' ' -f1)" = "$sum" ]; then
|
||||||
|
ok=$((ok + 1))
|
||||||
|
else
|
||||||
|
echo " themes: $name is a real file with other content — left alone"
|
||||||
|
kept=$((kept + 1))
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
echo " themes: would fetch $name"
|
||||||
|
fetched=$((fetched + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
tmp="$(mktemp "$dst.XXXXXX")"
|
||||||
|
if curl -fsSL --connect-timeout 5 --max-time 30 "$url" -o "$tmp" \
|
||||||
|
&& [ "$(sha256sum <"$tmp" | cut -d' ' -f1)" = "$sum" ]; then
|
||||||
|
chmod 644 "$tmp"
|
||||||
|
mv -f "$tmp" "$dst"
|
||||||
|
fetched=$((fetched + 1))
|
||||||
|
else
|
||||||
|
rm -f "$tmp"
|
||||||
|
echo " themes: could not fetch $name (offline, or the checksum changed) — app uses its built-in theme" >&2
|
||||||
|
bad=$((bad + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
verb="fetched"
|
||||||
|
[ "$DRY" = 1 ] && verb="would fetch"
|
||||||
|
echo " themes: $verb $fetched, $ok already present, $kept kept as local, $bad problem(s)"
|
||||||
|
[ "$bad" -eq 0 ]
|
||||||
@@ -115,6 +115,7 @@ __prompt() {
|
|||||||
PROMPT_COMMAND=__prompt
|
PROMPT_COMMAND=__prompt
|
||||||
|
|
||||||
set -o vi
|
set -o vi
|
||||||
|
eval "$(fzf --bash)"
|
||||||
# eval "$(zoxide init --cmd cd bash)"
|
# eval "$(zoxide init --cmd cd bash)"
|
||||||
|
|
||||||
# colorized --help
|
# colorized --help
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ clients:
|
|||||||
- name: GLM-4.7-Flash-UD-Q3_K_XL
|
- name: GLM-4.7-Flash-UD-Q3_K_XL
|
||||||
max_input_tokens: 22000 # ctx 24576 · ~28 t/s — fast coder (3-bit)
|
max_input_tokens: 22000 # ctx 24576 · ~28 t/s — fast coder (3-bit)
|
||||||
- name: Ornith-1.5-35B-A3B-IQ3_XXS
|
- name: Ornith-1.5-35B-A3B-IQ3_XXS
|
||||||
max_input_tokens: 22000 # ctx 24576 · ~32 t/s — agentic coder, thinking
|
max_input_tokens: 30000 # ctx 32768 · ~32 t/s — agentic coder, thinking
|
||||||
supports_vision: true
|
supports_vision: true
|
||||||
- name: gpt-oss-20b
|
- name: gpt-oss-20b
|
||||||
max_input_tokens: 62000 # ctx 65536 · ~38 t/s — fast reasoning + tools
|
max_input_tokens: 62000 # ctx 65536 · ~38 t/s — fast reasoning + tools
|
||||||
|
|||||||
@@ -1,663 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
Name: One Half Dark
|
|
||||||
Author: Son A. Pham <sp@sonpham.me>
|
|
||||||
Url: https://github.com/sonph/onehalf
|
|
||||||
License: The MIT License (MIT)
|
|
||||||
|
|
||||||
A dark Sublime Text color scheme based on Atom's One. See
|
|
||||||
github.com/sonph/onehalf for installation instructions, a light color
|
|
||||||
scheme, and versions for other editors/terminals such as Vim or iTerm.
|
|
||||||
|
|
||||||
Red: #e06c75
|
|
||||||
Green: #98c379
|
|
||||||
Yellow: #e5c07b
|
|
||||||
Blue: #61afef
|
|
||||||
Purple: #c678dd
|
|
||||||
Cyan: #56b6c2
|
|
||||||
White: #dcdfe4
|
|
||||||
Black: #282c34
|
|
||||||
Fg: #dcdfe4
|
|
||||||
Bg: #282c34
|
|
||||||
Comment: #5c6370
|
|
||||||
Gutter foreground: #919baa
|
|
||||||
Gutter background: #282c34
|
|
||||||
Selection: #474e5d
|
|
||||||
-->
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>OneHalfLight</string>
|
|
||||||
<key>semanticClass</key>
|
|
||||||
<string>theme.dark.one_half_dark</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string></string>
|
|
||||||
<key>colorSpaceName</key>
|
|
||||||
<string>sRGB</string>
|
|
||||||
<key>author</key>
|
|
||||||
<string>Son A. Pham <sp@sonpham.me></string>
|
|
||||||
<key>comment</key>
|
|
||||||
<string>A dark iTerm color scheme based on Atom's One. See github.com/sonph/onehalf for installation instructions, a light color scheme, and versions for other editors/terminals such as (Neo)Vim and Sublime Text.</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string></string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#282c34</string>
|
|
||||||
<key>bracketsOptions</key>
|
|
||||||
<string>underline</string>
|
|
||||||
<key>caret</key>
|
|
||||||
<string>#a3b3cc</string>
|
|
||||||
<key>gutter</key>
|
|
||||||
<string>#282c34</string>
|
|
||||||
<key>gutterForeground</key>
|
|
||||||
<string>#919baa</string>
|
|
||||||
<key>invisibles</key>
|
|
||||||
<string>#5c6370</string>
|
|
||||||
<key>lineHighlight</key>
|
|
||||||
<string>#313640</string>
|
|
||||||
<key>selection</key>
|
|
||||||
<string>#474e5d</string>
|
|
||||||
<key>selectionBorder</key>
|
|
||||||
<string>#474e5d</string>
|
|
||||||
<key>tagsForeground</key>
|
|
||||||
<string></string>
|
|
||||||
<key>tagsOptions</key>
|
|
||||||
<string>stippled_underline</string>
|
|
||||||
<key>bracketContentsOptions</key>
|
|
||||||
<string>underline</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Comments</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>comment</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#5c6370</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Text</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable.parameter.function</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Delimiters</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>none</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Operators</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword.operator</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Keywords</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Variables</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Functions</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.function, meta.require, support.function.any-method</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Classes</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>support.class, entity.name.class, entity.name.type.class</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Classes</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>meta.class</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Methods</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword.other.special-method</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Storage</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>storage</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Support</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>support.function</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Strings, Inherited Class</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>string</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Integers</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.numeric</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Floats</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>none</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Boolean</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>none</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Constants</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>HTML: Tags</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.tag</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>HTML: Tag attributes</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.other.attribute-name</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Attribute IDs</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.other.attribute-name.id, punctuation.definition.entity</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Selector</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>meta.selector</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Headings</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string></string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Bold</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.bold, punctuation.definition.bold</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Italic</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.italic, punctuation.definition.italic</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Code</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.raw.inline</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Link Text</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>string.other.link, punctuation.definition.string.end.markdown</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Link Url</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>meta.link</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Lists</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.list</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markdown: Quotes</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.quote</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Java Source</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.java meta.class.java meta.method.java</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Java Class Body</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.java meta.class.java meta.class.body.java</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Function Arguments</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.function.js variable.parameter.function.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: New Variables</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js variable.other.readwrite.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Variables</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js variable.other.object.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Variables in Function Calls</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.function-call.method.js variable.other.readwrite.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: New Block Variables</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.block.js variable.other.readwrite.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Block Variables</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.block.js variable.other.object.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Block Variables in Function Calls</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.block.js meta.function-call.method.js variable.other.readwrite.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Function Calls</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.function-call.method.js variable.function.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Properties</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js meta.property.object.js entity.name.function.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Javascript: Prototypes</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>source.js support.constant.prototype.js</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Separator</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>meta.separator</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string></string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Inserted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.inserted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Deleted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.deleted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Changed</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.changed</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Regular Expressions</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>string.regexp</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Escape Characters</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.character.escape</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#56b6c2</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Embedded</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>punctuation.section.embedded, variable.interpolation</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Illegal</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid.illegal</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Broken</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid.broken</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Deprecated</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid.deprecated</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#e5c07b</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Unimplemented</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid.unimplemented</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#c678dd</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#dcdfe4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes`
|
# Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes`
|
||||||
# for a list of all available themes
|
# for a list of all available themes
|
||||||
# Custom theme lives in ./themes/ — run `bat cache --build` after changing it
|
# OneHalfDark is built into bat — no custom themes are tracked, so no `bat cache --build` step
|
||||||
--theme="OneHalfDark"
|
--theme="OneHalfDark"
|
||||||
--style="numbers,changes,header,snip"
|
--style="numbers,changes,header,snip"
|
||||||
|
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>One Dark (NvChad base46)</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>3c1e2b84-9d5f-4a7c-8b2e-6f4a1d9c7e5b</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#1e222a</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#abb2bf</string>
|
|
||||||
<key>caret</key>
|
|
||||||
<string>#abb2bf</string>
|
|
||||||
<key>selection</key>
|
|
||||||
<string>#3e4451</string>
|
|
||||||
<key>lineHighlight</key>
|
|
||||||
<string>#252931</string>
|
|
||||||
<key>gutterForeground</key>
|
|
||||||
<string>#545862</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Comment</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>comment, punctuation.definition.comment</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#6f737b</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>String</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>string</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Number, constant</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.numeric, constant.language, constant.other</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#d19a66</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Escape character</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.character.escape</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#a3b8ef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Keyword, storage</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword, storage</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#c882e7</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Operator</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword.operator</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#abb2bf</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Function</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.function, support.function, meta.function-call.generic</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Class, type</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, support.type, support.class</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e7c787</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Variable</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable, variable.other</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Function parameter</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable.parameter</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Attribute</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.other.attribute-name</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#d19a66</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Tag</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.tag, meta.tag</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup heading</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.heading, entity.name.section</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>bold</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup bold</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.bold</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>bold</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup italic</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.italic</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup link</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.underline.link, string.other.link</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#61afef</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup quote</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.quote</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#6f737b</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Diff inserted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.inserted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#98c379</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Diff deleted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.deleted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#e06c75</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Invalid</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid, invalid.illegal</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ffffff</string>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#be5046</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Synthwave 84</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>7b2c3a10-4b6e-4d8f-9c1a-2e5f8d7a9b3c</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#262335</string>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ffffff</string>
|
|
||||||
<key>caret</key>
|
|
||||||
<string>#f97e72</string>
|
|
||||||
<key>selection</key>
|
|
||||||
<string>#463465</string>
|
|
||||||
<key>lineHighlight</key>
|
|
||||||
<string>#2a2139</string>
|
|
||||||
<key>gutterForeground</key>
|
|
||||||
<string>#848bbd</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Comment</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>comment, punctuation.definition.comment</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#848bbd</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>String</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>string</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ff8b39</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Number, constant</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.numeric, constant.language, constant.other</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#f97e72</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Escape character</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>constant.character.escape</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#36f9f6</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Keyword, storage</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>keyword, storage, keyword.operator</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fede5d</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Storage type</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>storage.type, storage.modifier</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fede5d</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Function</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.function, support.function, meta.function-call.generic</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#36f9f6</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Class, type</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, support.type, support.class</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fe4450</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Variable</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable, variable.other</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ff7edb</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Function parameter</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>variable.parameter</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ff7edb</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Attribute</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.other.attribute-name</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fede5d</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Tag</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>entity.name.tag, meta.tag</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#72f1b8</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup heading</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.heading, entity.name.section</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fe4450</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>bold</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup bold</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.bold</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>bold</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup italic</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.italic</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup link</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.underline.link, string.other.link</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#36f9f6</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Markup quote</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.quote</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#848bbd</string>
|
|
||||||
<key>fontStyle</key>
|
|
||||||
<string>italic</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Diff inserted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.inserted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#72f1b8</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Diff deleted</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>markup.deleted</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#fe4450</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>Invalid</string>
|
|
||||||
<key>scope</key>
|
|
||||||
<string>invalid, invalid.illegal</string>
|
|
||||||
<key>settings</key>
|
|
||||||
<dict>
|
|
||||||
<key>foreground</key>
|
|
||||||
<string>#ffffff</string>
|
|
||||||
<key>background</key>
|
|
||||||
<string>#fe4450</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
+4
-294
@@ -1,241 +1,13 @@
|
|||||||
## Configuration file for CAVA.
|
## cava — only this repo's own settings. Every key left out falls back to cava's built-in
|
||||||
# Remove the ; to change parameters.
|
## default; cava documents them all in the config it writes to an empty ~/.config/cava/config,
|
||||||
|
## and it regenerates its own shaders/ and themes/ there on first run (so the repo ships none
|
||||||
|
## of cava's files).
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
|
|
||||||
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
|
|
||||||
# mode = scientific # deprecated since cava 0.6.0, ignored
|
|
||||||
|
|
||||||
# Accepts only non-negative values.
|
|
||||||
framerate = 60
|
framerate = 60
|
||||||
|
|
||||||
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
|
|
||||||
# new as of 0.6.0 autosens of low values (dynamic range)
|
|
||||||
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
|
|
||||||
autosens = 1
|
autosens = 1
|
||||||
; overshoot = 20
|
|
||||||
|
|
||||||
# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.
|
|
||||||
# 200 means double height. Accepts only non-negative values.
|
|
||||||
; sensitivity = 100
|
|
||||||
|
|
||||||
# The number of bars (0-512). 0 sets it to auto (fill up console).
|
|
||||||
# Bars' width and space between bars in number of characters.
|
|
||||||
; bars = 0
|
|
||||||
; bar_width = 2
|
|
||||||
; bar_spacing = 1
|
|
||||||
# bar_height is only used for output in "noritake" format
|
|
||||||
; bar_height = 32
|
|
||||||
|
|
||||||
# For SDL width and space between bars is in pixels, defaults are:
|
|
||||||
; bar_width = 20
|
|
||||||
; bar_spacing = 5
|
|
||||||
|
|
||||||
# sdl_glsl have these default values, they are only used to calculate max number of bars.
|
|
||||||
; bar_width = 1
|
|
||||||
; bar_spacing = 0
|
|
||||||
|
|
||||||
# ceter bars in terminal, if there is space.
|
|
||||||
; center_align = 1
|
|
||||||
|
|
||||||
# max height of bars in terminal, in percent of terminal height.
|
|
||||||
; max_height = 100
|
|
||||||
|
|
||||||
|
|
||||||
# Lower and higher cutoff frequencies for lowest and highest bars
|
|
||||||
# the bandwidth of the visualizer.
|
|
||||||
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
|
|
||||||
# Cava will automatically increase the higher cutoff if a too low band is specified.
|
|
||||||
; lower_cutoff_freq = 50
|
|
||||||
; higher_cutoff_freq = 10000
|
|
||||||
|
|
||||||
|
|
||||||
# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
|
|
||||||
# only check for input once per second. Cava will wake up once input is detected. 0 = disable.
|
|
||||||
; sleep_timer = 0
|
|
||||||
|
|
||||||
|
|
||||||
[input]
|
|
||||||
|
|
||||||
# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'
|
|
||||||
# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.
|
|
||||||
# On Mac it defaults to 'portaudio' or 'fifo'
|
|
||||||
# On windows this is automatic and no input settings are needed.
|
|
||||||
#
|
|
||||||
# All input methods uses the same config variable 'source'
|
|
||||||
# to define where it should get the audio.
|
|
||||||
#
|
|
||||||
# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
|
|
||||||
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
|
|
||||||
#
|
|
||||||
# For pipewire 'source' will be the object name or object.serial of the device to capture from.
|
|
||||||
# Both input and output devices are supported. To capture the monitor source of a sink node, append '.monitor' to the sink's object name.
|
|
||||||
#
|
|
||||||
# For alsa 'source' will be the capture device.
|
|
||||||
# For fifo 'source' will be the path to fifo-file.
|
|
||||||
# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
|
|
||||||
#
|
|
||||||
# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.
|
|
||||||
# README.md contains further information on how to setup CAVA for sndio.
|
|
||||||
#
|
|
||||||
# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.
|
|
||||||
# README.md contains further information on how to setup CAVA for OSS on FreeBSD.
|
|
||||||
#
|
|
||||||
# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.
|
|
||||||
# README.md contains further information on how to setup CAVA for JACK.
|
|
||||||
#
|
|
||||||
; method = pulse
|
|
||||||
; source = auto
|
|
||||||
|
|
||||||
; method = pipewire
|
|
||||||
; source = auto
|
|
||||||
|
|
||||||
; method = alsa
|
|
||||||
; source = hw:Loopback,1
|
|
||||||
|
|
||||||
; method = fifo
|
|
||||||
; source = /tmp/mpd.fifo
|
|
||||||
|
|
||||||
; method = shmem
|
|
||||||
; source = /squeezelite-AA:BB:CC:DD:EE:FF
|
|
||||||
|
|
||||||
; method = portaudio
|
|
||||||
; source = auto
|
|
||||||
|
|
||||||
; method = sndio
|
|
||||||
; source = default
|
|
||||||
|
|
||||||
; method = oss
|
|
||||||
; source = /dev/dsp
|
|
||||||
|
|
||||||
; method = jack
|
|
||||||
; source = default
|
|
||||||
|
|
||||||
# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
|
|
||||||
# sample_rate: fifo, pipewire, sndio, oss
|
|
||||||
# sample_bits: fifo, pipewire, sndio, oss
|
|
||||||
# channels: sndio, oss, jack
|
|
||||||
# autoconnect: jack
|
|
||||||
# Other methods ignore these settings.
|
|
||||||
#
|
|
||||||
# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported
|
|
||||||
# by the chosen audio device, the device will use other supported values instead.
|
|
||||||
# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it
|
|
||||||
# will use 44100, 16 and 1.
|
|
||||||
#
|
|
||||||
; sample_rate = 44100
|
|
||||||
; sample_bits = 16
|
|
||||||
; channels = 2
|
|
||||||
; autoconnect = 2
|
|
||||||
|
|
||||||
|
|
||||||
[output]
|
|
||||||
|
|
||||||
# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'
|
|
||||||
# or 'sdl_glsl'.
|
|
||||||
# 'noncurses' (default) uses a buffer and cursor movements to only print
|
|
||||||
# changes from frame to frame in the terminal. Uses less resources and is less
|
|
||||||
# prone to tearing (vsync issues) than 'ncurses'.
|
|
||||||
#
|
|
||||||
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
|
|
||||||
# stream of the bar heights that can be used to send to other applications.
|
|
||||||
# 'raw' defaults to 1024 bars stereo (512 bars mono), which can be adjusted in the 'bars' option above.
|
|
||||||
#
|
|
||||||
# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display
|
|
||||||
# in graphic mode. It only support the 3000 series graphical VFDs for now.
|
|
||||||
#
|
|
||||||
# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.
|
|
||||||
# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or
|
|
||||||
# use one of the predefined ones.
|
|
||||||
; method = noncurses
|
|
||||||
|
|
||||||
# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or
|
|
||||||
# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl
|
|
||||||
# and ncruses output. 'horizontal' (bars go up and down from center) is only supported
|
|
||||||
# on noncurses output.
|
|
||||||
# Note: many fonts have weird or missing glyphs for characters used in orientations
|
|
||||||
# other than 'bottom', which can make output not look right.
|
|
||||||
; orientation = bottom
|
|
||||||
|
|
||||||
# Visual channels. Can be 'stereo' or 'mono'.
|
|
||||||
# 'stereo' mirrors both channels with low frequencies in center.
|
|
||||||
# 'mono' outputs left to right lowest to highest frequencies.
|
|
||||||
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
|
|
||||||
# set 'reverse' to 1 to display frequencies the other way around.
|
|
||||||
; channels = stereo
|
|
||||||
; mono_option = average
|
|
||||||
; reverse = 0
|
|
||||||
|
|
||||||
# Raw output target.
|
|
||||||
# On Linux, a fifo will be created if target does not exist.
|
|
||||||
# On Windows, a named pipe will be created if target does not exist.
|
|
||||||
; raw_target = /dev/stdout
|
|
||||||
|
|
||||||
# Raw data format. Can be 'binary' or 'ascii'.
|
|
||||||
; data_format = binary
|
|
||||||
|
|
||||||
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
|
|
||||||
; bit_format = 16bit
|
|
||||||
|
|
||||||
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
|
|
||||||
; ascii_max_range = 1000
|
|
||||||
|
|
||||||
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
|
|
||||||
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
|
|
||||||
; bar_delimiter = 59
|
|
||||||
; frame_delimiter = 10
|
|
||||||
|
|
||||||
# sdl window size and position. -1,-1 is centered.
|
|
||||||
; sdl_width = 1024
|
|
||||||
; sdl_height = 512
|
|
||||||
; sdl_x = -1
|
|
||||||
; sdl_y= -1
|
|
||||||
; sdl_full_screen = 0
|
|
||||||
|
|
||||||
# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'
|
|
||||||
# 'frequency' displays the lower cut off frequency of the bar above.
|
|
||||||
# Only supported on ncurses and noncurses output.
|
|
||||||
; xaxis = none
|
|
||||||
|
|
||||||
# enable synchronized sync. 1 = on, 0 = off
|
|
||||||
# removes flickering in alacritty terminal emulator.
|
|
||||||
# defaults to off since the behaviour in other terminal emulators is unknown
|
|
||||||
; synchronized_sync = 0
|
|
||||||
|
|
||||||
# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders
|
|
||||||
; vertex_shader = pass_through.vert
|
|
||||||
; fragment_shader = bar_spectrum.frag
|
|
||||||
|
|
||||||
; for glsl output mode, keep rendering even if no audio
|
|
||||||
; continuous_rendering = 0
|
|
||||||
|
|
||||||
# disable console blank (screen saver) in tty
|
|
||||||
# (Not supported on FreeBSD)
|
|
||||||
; disable_blanking = 0
|
|
||||||
|
|
||||||
# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off
|
|
||||||
; show_idle_bar_heads = 1
|
|
||||||
|
|
||||||
# show waveform instead of frequency spectrum, 1 = on, 0 = off
|
|
||||||
; waveform = 0
|
|
||||||
|
|
||||||
[color]
|
[color]
|
||||||
|
|
||||||
# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
|
|
||||||
# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
|
|
||||||
# a terminal that can change color definitions such as Gnome-terminal or rxvt.
|
|
||||||
# default is to keep current terminal color
|
|
||||||
; background = default
|
|
||||||
; foreground = default
|
|
||||||
|
|
||||||
# SDL and sdl_glsl only support hex code colors, these are the default:
|
|
||||||
; background = '#111111'
|
|
||||||
; foreground = '#D34b11'
|
|
||||||
# ; foreground = '#D34b11'
|
|
||||||
|
|
||||||
# Gradient mode, only hex defined colors are supported,
|
|
||||||
# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
|
|
||||||
# You can define as many as 8 different colors. They range from bottom to top of screen
|
|
||||||
gradient = 1
|
gradient = 1
|
||||||
gradient_color_1 = '#d34b11'
|
gradient_color_1 = '#d34b11'
|
||||||
gradient_color_2 = '#cc5933'
|
gradient_color_2 = '#cc5933'
|
||||||
@@ -245,65 +17,3 @@ gradient_color_5 = '#cccc33'
|
|||||||
gradient_color_6 = '#a6cc33'
|
gradient_color_6 = '#a6cc33'
|
||||||
gradient_color_7 = '#80cc33'
|
gradient_color_7 = '#80cc33'
|
||||||
gradient_color_8 = '#59cc33'
|
gradient_color_8 = '#59cc33'
|
||||||
|
|
||||||
# Horizontal is only supported on noncurses output.
|
|
||||||
# Only one color will be calculated per bar.
|
|
||||||
; horizontal_gradient = 1
|
|
||||||
; horizontal_gradient_color_1 = '#c45161'
|
|
||||||
; horizontal_gradient_color_2 = '#e094a0'
|
|
||||||
; horizontal_gradient_color_3 = '#f2b6c0'
|
|
||||||
; horizontal_gradient_color_4 = '#f2dde1'
|
|
||||||
; horizontal_gradient_color_5 = '#cbc7d8'
|
|
||||||
; horizontal_gradient_color_6 = '#8db7d2'
|
|
||||||
; horizontal_gradient_color_7 = '#5e62a9'
|
|
||||||
; horizontal_gradient_color_8 = '#434279'
|
|
||||||
|
|
||||||
|
|
||||||
# If both vertical and horizontal gradient is enabled, vertical will be blended in this direction.
|
|
||||||
# Can be 'up', 'down', 'left' or 'right'. 'up' means the vertical gradient will be blended in from
|
|
||||||
# bottom to top. I.e. the bottom will be only the horizontal
|
|
||||||
# and top will be only the color of the vertical gradient.
|
|
||||||
; blend_direction = 'up'
|
|
||||||
|
|
||||||
# use theme file instead of defining colors in this file
|
|
||||||
# themes are located in $HOME/.config/cava/themes
|
|
||||||
; theme = 'none'
|
|
||||||
|
|
||||||
|
|
||||||
[smoothing]
|
|
||||||
|
|
||||||
# Percentage value for integral smoothing. Takes values from 0 - 100.
|
|
||||||
# Higher values means smoother, but less precise. 0 to disable.
|
|
||||||
# DEPRECATED as of 0.8.0, use noise_reduction instead
|
|
||||||
; integral = 77
|
|
||||||
|
|
||||||
# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
|
|
||||||
; monstercat = 0
|
|
||||||
; waves = 0
|
|
||||||
|
|
||||||
# Set gravity percentage for "drop off". Higher values means bars will drop faster.
|
|
||||||
# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
|
|
||||||
# DEPRECATED as of 0.8.0, use noise_reduction instead
|
|
||||||
; gravity = 100
|
|
||||||
|
|
||||||
|
|
||||||
# In bar height, bars that would have been lower that this will not be drawn.
|
|
||||||
# DEPRECATED as of 0.8.0
|
|
||||||
; ignore = 0
|
|
||||||
|
|
||||||
# Noise reduction, int 0 - 100. default 77
|
|
||||||
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
|
|
||||||
# 100 will be very slow and smooth, 0 will be fast but noisy.
|
|
||||||
; noise_reduction = 77
|
|
||||||
|
|
||||||
|
|
||||||
[eq]
|
|
||||||
|
|
||||||
# This one is tricky. You can have as much keys as you want.
|
|
||||||
# Remember to uncomment more than one key! More keys = more precision.
|
|
||||||
# Look at readme.md on github for further explanations and examples.
|
|
||||||
; 1 = 1 # bass
|
|
||||||
; 2 = 1
|
|
||||||
; 3 = 1 # midtone
|
|
||||||
; 4 = 1
|
|
||||||
; 5 = 1 # treble
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
in vec2 fragCoord;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
|
||||||
uniform float bars[512];
|
|
||||||
|
|
||||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
|
||||||
uniform int bar_width; // bar width (configurable), not used here
|
|
||||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
|
||||||
|
|
||||||
uniform vec3 u_resolution; // window resolution
|
|
||||||
|
|
||||||
//colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
|
||||||
uniform vec3 bg_color; // background color
|
|
||||||
uniform vec3 fg_color; // foreground color
|
|
||||||
|
|
||||||
uniform int gradient_count;
|
|
||||||
uniform vec3 gradient_colors[8]; // gradient colors
|
|
||||||
|
|
||||||
vec3 normalize_C(float y,vec3 col_1, vec3 col_2, float y_min, float y_max)
|
|
||||||
{
|
|
||||||
//create color based on fraction of this color and next color
|
|
||||||
float yr = (y - y_min) / (y_max - y_min);
|
|
||||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
// find which bar to use based on where we are on the x axis
|
|
||||||
float x = u_resolution.x * fragCoord.x;
|
|
||||||
int bar = int(bars_count * fragCoord.x);
|
|
||||||
|
|
||||||
//calculate a bar size
|
|
||||||
float bar_size = u_resolution.x / bars_count;
|
|
||||||
|
|
||||||
//the y coordinate and bar values are the same
|
|
||||||
float y = bars[bar];
|
|
||||||
|
|
||||||
// make sure there is a thin line at bottom
|
|
||||||
if (y * u_resolution.y < 1.0)
|
|
||||||
{
|
|
||||||
y = 1.0 / u_resolution.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
//draw the bar up to current height
|
|
||||||
if (y > fragCoord.y)
|
|
||||||
{
|
|
||||||
//make some space between bars basen on settings
|
|
||||||
if (x > (bar + 1) * (bar_size) - bar_spacing)
|
|
||||||
{
|
|
||||||
fragColor = vec4(bg_color,1.0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (gradient_count == 0)
|
|
||||||
{
|
|
||||||
fragColor = vec4(fg_color,1.0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//find which color in the configured gradient we are at
|
|
||||||
int color = int((gradient_count - 1) * fragCoord.y);
|
|
||||||
|
|
||||||
//find where on y this and next color is supposed to be
|
|
||||||
float y_min = color / (gradient_count - 1.0);
|
|
||||||
float y_max = (color + 1.0) / (gradient_count - 1.0);
|
|
||||||
|
|
||||||
//make color
|
|
||||||
fragColor = vec4(normalize_C(fragCoord.y, gradient_colors[color], gradient_colors[color + 1], y_min, y_max), 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fragColor = vec4(bg_color,1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
// this shader was stolen from shadertoy user ChunderFPV
|
|
||||||
|
|
||||||
#define SCALE 8.0
|
|
||||||
#define PI radians(180.0)
|
|
||||||
#define TAU (PI * 2.0)
|
|
||||||
#define CS(a) vec2(cos(a), sin(a))
|
|
||||||
#define PT(u, r) smoothstep(0.0, r, r - length(u))
|
|
||||||
|
|
||||||
in vec2 fragCoord;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
uniform float bars[512];
|
|
||||||
|
|
||||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
|
||||||
uniform float shader_time; // shader execution time s
|
|
||||||
uniform int bar_width; // bar width (configurable), not used here
|
|
||||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
|
||||||
|
|
||||||
uniform vec3 u_resolution; // window resolution
|
|
||||||
|
|
||||||
// colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
|
||||||
uniform vec3 bg_color; // background color
|
|
||||||
uniform vec3 fg_color; // foreground color
|
|
||||||
|
|
||||||
uniform int gradient_count;
|
|
||||||
uniform vec3 gradient_colors[8]; // gradient colors
|
|
||||||
|
|
||||||
// gradient map ( color, equation, time, width, shadow, reciprocal )
|
|
||||||
vec3 gm(vec3 c, float n, float t, float w, float d, bool i) {
|
|
||||||
float g = min(abs(n), 1.0 / abs(n));
|
|
||||||
float s = abs(sin(n * PI - t));
|
|
||||||
if (i)
|
|
||||||
s = min(s, abs(sin(PI / n + t)));
|
|
||||||
return (1.0 - pow(abs(s), w)) * c * pow(g, d) * 6.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// denominator spiral, use 1/n for numerator
|
|
||||||
// ( screen xy, spiral exponent, decimal, line width, hardness, rotation )
|
|
||||||
float ds(vec2 u, float e, float n, float w, float h, float ro) {
|
|
||||||
float ur = length(u); // unit radius
|
|
||||||
float sr = pow(ur, e); // spiral radius
|
|
||||||
float a = round(sr) * n * TAU; // arc
|
|
||||||
vec2 xy = CS(a + ro) * ur; // xy coords
|
|
||||||
float l = PT(u - xy, w); // line
|
|
||||||
float s = mod(sr + 0.5, 1.0); // gradient smooth
|
|
||||||
s = min(s, 1.0 - s); // darken filter
|
|
||||||
return l * s * h;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float t = shader_time / PI * 2.0;
|
|
||||||
vec4 m = vec4(0, 0, 0, 0); // iMouse;
|
|
||||||
m.xy = m.xy * 2.0 / u_resolution.xy - 1.0; // ±1x, ±1y
|
|
||||||
if (m.z > 0.0)
|
|
||||||
t += m.y * SCALE; // move time with mouse y
|
|
||||||
float z = (m.z > 0.0) ? pow(1.0 - abs(m.y), sign(m.y)) : 1.0; // zoom (+)
|
|
||||||
float e = (m.z > 0.0) ? pow(1.0 - abs(m.x), -sign(m.x))
|
|
||||||
: 1.0; // screen exponent (+)
|
|
||||||
float se = (m.z > 0.0) ? e * -sign(m.y) : 1.0; // spiral exponent
|
|
||||||
vec3 bg = vec3(0); // black background
|
|
||||||
|
|
||||||
float aa = 3.0; // anti-aliasing
|
|
||||||
|
|
||||||
for (float j = 0.0; j < aa; j++)
|
|
||||||
for (float k = 0.0; k < aa; k++) {
|
|
||||||
vec3 c = vec3(0);
|
|
||||||
vec2 o = vec2(j, k) / aa;
|
|
||||||
vec2 uv = (fragCoord * u_resolution.xy - 0.5 * u_resolution.xy + o) /
|
|
||||||
u_resolution.y * SCALE * z; // apply cartesian, scale and zoom
|
|
||||||
if (m.z > 0.0)
|
|
||||||
uv =
|
|
||||||
exp(log(abs(uv)) * e) * sign(uv); // warp screen space with exponent
|
|
||||||
|
|
||||||
float px = length(fwidth(uv)); // pixel width
|
|
||||||
float x = uv.x; // every pixel on x
|
|
||||||
float y = uv.y; // every pixel on y
|
|
||||||
float l = length(uv); // hypot of xy: sqrt(x*x+y*y)
|
|
||||||
|
|
||||||
float mc = (x * x + y * y - 1.0) / y; // metallic circle at xy
|
|
||||||
float g = min(abs(mc), 1.0 / abs(mc)); // gradient
|
|
||||||
vec3 gold = vec3(1.0, 0.6, 0.0) * g * l;
|
|
||||||
vec3 blue = vec3(0.3, 0.5, 0.9) * (1.0 - g);
|
|
||||||
vec3 rgb = max(gold, blue);
|
|
||||||
|
|
||||||
float w = 0.1; // line width
|
|
||||||
float d = 0.4; // shadow depth
|
|
||||||
c = max(c, gm(rgb, mc, -t, w * bars[0], d, false)); // metallic
|
|
||||||
c = max(c, gm(rgb, abs(y / x) * sign(y), -t, w * bars[1], d,
|
|
||||||
false)); // tangent
|
|
||||||
c = max(c, gm(rgb, (x * x) / (y * y) * sign(y), -t, w * bars[2], d,
|
|
||||||
false)); // sqrt cotangent
|
|
||||||
c = max(c, gm(rgb, (x * x) + (y * y), t, w * bars[3], d,
|
|
||||||
true)); // sqrt circles
|
|
||||||
|
|
||||||
c += rgb * ds(uv, se, t / TAU, px * 2.0 * bars[4], 2.0, 0.0); // spiral 1a
|
|
||||||
c += rgb * ds(uv, se, t / TAU, px * 2.0 * bars[5], 2.0, PI); // spiral 1b
|
|
||||||
c +=
|
|
||||||
rgb * ds(uv, -se, t / TAU, px * 2.0 * bars[6], 2.0, 0.0); // spiral 2a
|
|
||||||
c += rgb * ds(uv, -se, t / TAU, px * 2.0 * bars[7], 2.0, PI); // spiral 2b
|
|
||||||
c = max(c, 0.0); // clear negative color
|
|
||||||
|
|
||||||
c += pow(max(1.0 - l, 0.0), 3.0 / z); // center glow
|
|
||||||
|
|
||||||
if (m.z > 0.0) // display grid on click
|
|
||||||
{
|
|
||||||
vec2 xyg = abs(fract(uv + 0.5) - 0.5) / px; // xy grid
|
|
||||||
c.gb += 0.2 * (1.0 - min(min(xyg.x, xyg.y), 1.0));
|
|
||||||
}
|
|
||||||
bg += c;
|
|
||||||
}
|
|
||||||
bg /= aa * aa;
|
|
||||||
bg *= sqrt(bg) * 1.5;
|
|
||||||
|
|
||||||
fragColor = vec4(bg, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
in vec2 fragCoord;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
|
||||||
uniform float bars[512];
|
|
||||||
|
|
||||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
|
||||||
|
|
||||||
uniform vec3 u_resolution; // window resolution, not used here
|
|
||||||
|
|
||||||
//colors, configurable in cava config file
|
|
||||||
uniform vec3 bg_color; // background color(r,g,b) (0.0 - 1.0), not used here
|
|
||||||
uniform vec3 fg_color; // foreground color, not used here
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
// find which bar to use based on where we are on the x axis
|
|
||||||
int bar = int(bars_count * fragCoord.x);
|
|
||||||
|
|
||||||
float bar_y = 1.0 - abs((fragCoord.y - 0.5)) * 2.0;
|
|
||||||
float y = (bars[bar]) * bar_y;
|
|
||||||
|
|
||||||
float bar_x = (fragCoord.x - float(bar) / float(bars_count)) * bars_count;
|
|
||||||
float bar_r = 1.0 - abs((bar_x - 0.5)) * 2;
|
|
||||||
|
|
||||||
bar_r = bar_r * bar_r * 2;
|
|
||||||
|
|
||||||
// set color
|
|
||||||
fragColor.r = fg_color.x * y * bar_r;
|
|
||||||
fragColor.g = fg_color.y * y * bar_r;
|
|
||||||
fragColor.b = fg_color.z * y * bar_r;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
|
|
||||||
// Input vertex data, different for all executions of this shader.
|
|
||||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
|
||||||
|
|
||||||
// Output data ; will be interpolated for each fragment.
|
|
||||||
out vec2 fragCoord;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
gl_Position = vec4(vertexPosition_modelspace,1);
|
|
||||||
fragCoord = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
in vec2 fragCoord;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
// bar values. defaults to left channels first (low to high), then right (high
|
|
||||||
// to low).
|
|
||||||
uniform float bars[512];
|
|
||||||
|
|
||||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
|
||||||
uniform int bar_width; // bar width (configurable), not used here
|
|
||||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
|
||||||
|
|
||||||
uniform vec3 u_resolution; // window resolution
|
|
||||||
|
|
||||||
// colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
|
||||||
uniform vec3 bg_color; // background color
|
|
||||||
uniform vec3 fg_color; // foreground color
|
|
||||||
|
|
||||||
uniform int gradient_count;
|
|
||||||
uniform vec3 gradient_colors[8]; // gradient colors
|
|
||||||
|
|
||||||
uniform sampler2D inputTexture; // Texture from the first render pass
|
|
||||||
|
|
||||||
vec3 normalize_C(float y, vec3 col_1, vec3 col_2, float y_min, float y_max) {
|
|
||||||
// create color based on fraction of this color and next color
|
|
||||||
float yr = (y - y_min) / (y_max - y_min);
|
|
||||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// find which bar to use based on where we are on the y axis
|
|
||||||
int bar = int(bars_count * fragCoord.y);
|
|
||||||
float y = bars[bar];
|
|
||||||
float band_size = 1.0 / float(bars_count);
|
|
||||||
float current_band_min = bar * band_size;
|
|
||||||
float current_band_max = (bar + 1) * band_size;
|
|
||||||
|
|
||||||
int hist_length = 512;
|
|
||||||
float win_size = 1.0 / hist_length;
|
|
||||||
|
|
||||||
if (fragCoord.x > 1.0 - win_size) {
|
|
||||||
|
|
||||||
if (fragCoord.y > current_band_min && fragCoord.y < current_band_max) {
|
|
||||||
|
|
||||||
fragColor = vec4(fg_color * y, 1.0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec2 offsetCoord = fragCoord;
|
|
||||||
offsetCoord.x += float(win_size);
|
|
||||||
fragColor = texture(inputTexture, offsetCoord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
// Emulate the "line style" spectrum analyzer from Winamp 2.
|
|
||||||
// Try this config for a demonstration:
|
|
||||||
|
|
||||||
/*
|
|
||||||
[general]
|
|
||||||
bar_width = 2
|
|
||||||
bar_spacing = 0
|
|
||||||
higher_cutoff_freq = 22000
|
|
||||||
|
|
||||||
[output]
|
|
||||||
method = sdl_glsl
|
|
||||||
channels = mono
|
|
||||||
fragment_shader = winamp_line_style_spectrum.frag
|
|
||||||
|
|
||||||
[color]
|
|
||||||
background = '#000000'
|
|
||||||
gradient = 1
|
|
||||||
gradient_color_1 = '#319C08'
|
|
||||||
gradient_color_2 = '#29CE10'
|
|
||||||
gradient_color_3 = '#BDDE29'
|
|
||||||
gradient_color_4 = '#DEA518'
|
|
||||||
gradient_color_5 = '#D66600'
|
|
||||||
gradient_color_6 = '#CE2910'
|
|
||||||
|
|
||||||
[smoothing]
|
|
||||||
noise_reduction = 10
|
|
||||||
*/
|
|
||||||
|
|
||||||
in vec2 fragCoord;
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
|
||||||
uniform float bars[512];
|
|
||||||
|
|
||||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
|
||||||
uniform int bar_width; // bar width (configurable), not used here
|
|
||||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
|
||||||
|
|
||||||
uniform vec3 u_resolution; // window resolution
|
|
||||||
|
|
||||||
//colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
|
||||||
uniform vec3 bg_color; // background color
|
|
||||||
uniform vec3 fg_color; // foreground color
|
|
||||||
|
|
||||||
uniform int gradient_count;
|
|
||||||
uniform vec3 gradient_colors[8]; // gradient colors
|
|
||||||
|
|
||||||
vec3 normalize_C(float y,vec3 col_1, vec3 col_2, float y_min, float y_max)
|
|
||||||
{
|
|
||||||
//create color based on fraction of this color and next color
|
|
||||||
float yr = (y - y_min) / (y_max - y_min);
|
|
||||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
// find which bar to use based on where we are on the x axis
|
|
||||||
float x = u_resolution.x * fragCoord.x;
|
|
||||||
int bar = int(bars_count * fragCoord.x);
|
|
||||||
|
|
||||||
//calculate a bar size
|
|
||||||
float bar_size = u_resolution.x / bars_count;
|
|
||||||
|
|
||||||
//the y coordinate is stretched by 4X to resemble Winamp
|
|
||||||
float y = min(bars[bar] * 4.0, 1.0);
|
|
||||||
|
|
||||||
// make sure there is a thin line at bottom
|
|
||||||
if (y * u_resolution.y < 1.0)
|
|
||||||
{
|
|
||||||
y = 1.0 / u_resolution.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 bar_color;
|
|
||||||
|
|
||||||
if (gradient_count == 0)
|
|
||||||
{
|
|
||||||
bar_color = vec4(fg_color,1.0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//find color in the configured gradient for the top of the bar
|
|
||||||
int color = int((gradient_count - 1) * y);
|
|
||||||
|
|
||||||
//find where on y this and next color is supposed to be
|
|
||||||
float y_min = float(color) / (gradient_count - 1.0);
|
|
||||||
float y_max = float(color + 1) / (gradient_count - 1.0);
|
|
||||||
|
|
||||||
//make a solid color for the entire bar
|
|
||||||
bar_color = vec4(normalize_C(y, gradient_colors[color], gradient_colors[color + 1], y_min, y_max), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//draw the bar up to current height
|
|
||||||
if (y > fragCoord.y)
|
|
||||||
{
|
|
||||||
//make some space between bars based on settings
|
|
||||||
if (x > (bar + 1) * (bar_size) - bar_spacing)
|
|
||||||
{
|
|
||||||
fragColor = vec4(bg_color,1.0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fragColor = bar_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fragColor = vec4(bg_color,1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[color]
|
|
||||||
background = '#001e26'
|
|
||||||
foreground = '#708183'
|
|
||||||
|
|
||||||
gradient = 1
|
|
||||||
gradient_color_1 = '#268bd2'
|
|
||||||
gradient_color_2 = '#6c71c4'
|
|
||||||
gradient_color_3 = '#cb4b16'
|
|
||||||
|
|
||||||
horizontal_gradient = 1
|
|
||||||
horizontal_gradient_color_1 = '#586e75'
|
|
||||||
horizontal_gradient_color_2 = '#b58900'
|
|
||||||
horizontal_gradient_color_3 = '#839496'
|
|
||||||
|
|
||||||
blend_direction = 'up'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[color]
|
|
||||||
horizontal_gradient = 1
|
|
||||||
horizontal_gradient_color_1 = '#c45161'
|
|
||||||
horizontal_gradient_color_2 = '#e094a0'
|
|
||||||
horizontal_gradient_color_3 = '#f2b6c0'
|
|
||||||
horizontal_gradient_color_4 = '#f2dde1'
|
|
||||||
horizontal_gradient_color_5 = '#cbc7d8'
|
|
||||||
horizontal_gradient_color_6 = '#8db7d2'
|
|
||||||
horizontal_gradient_color_7 = '#5e62a9'
|
|
||||||
horizontal_gradient_color_8 = '#434279'
|
|
||||||
@@ -7,15 +7,20 @@ back into this repo.
|
|||||||
|
|
||||||
## What's tracked here
|
## What's tracked here
|
||||||
|
|
||||||
- `settings.json` — global settings: permissions (deny git commit/push, `gh pr` write ops, and
|
- `settings.json` — global settings: permissions (deny git push, `gh pr merge` / `close`,
|
||||||
reads of secret files), default `model`, `enabledPlugins`, vim mode, dark theme, `effortLevel`,
|
and reads of secret files; ask before `gh pr create` and `git rebase`; allow `gh` reads, `gh pr edit`, `gh workflow run`
|
||||||
|
and `gh run rerun`; `git commit` is left to `hooks/git-guard.py`, which exempts listed repos), default `model`, `enabledPlugins`, vim mode, dark theme, `effortLevel`,
|
||||||
fullscreen TUI, `statusLine`, `hooks`, `worktree` defaults, a generic `autoMode.environment`, …
|
fullscreen TUI, `statusLine`, `hooks`, `worktree` defaults, a generic `autoMode.environment`, …
|
||||||
Nothing in here may name a specific project, path or remote: this file is published by the
|
Nothing in here may name a specific project, path or remote: this file is published by the
|
||||||
public mirror, and the auto-mode classifier only reads `autoMode` from user/managed settings
|
public mirror, and the auto-mode classifier only reads `autoMode` from user/managed settings
|
||||||
anyway (never from a project's `.claude/settings*.json`).
|
anyway (never from a project's `.claude/settings*.json`).
|
||||||
- `statusline.py` — rich status line (dir, git, model, context %, cost, disk — system metrics live in the tmux bar).
|
- `statusline.py` — rich status line (dir, git, unresolved PR review threads, model, context %, cost, disk — system
|
||||||
|
metrics live in the tmux bar).
|
||||||
See [StatusBar.md](StatusBar.md) for what each segment and colour means.
|
See [StatusBar.md](StatusBar.md) for what each segment and colour means.
|
||||||
- `keybindings.json` — custom keybindings: vim-style scroll/navigation, plus `ctrl+x v` for the
|
- `keybindings.json` — custom keybindings: vim-style scroll/navigation (`alt+j/k` and
|
||||||
|
`ctrl+shift+j/k` scroll by line — kitty's own `kitty_mod+j/k` scroll only its main-screen
|
||||||
|
scrollback and pass the key through to the fullscreen TUI's alternate screen, so they do not
|
||||||
|
shadow these; dropped once by mistake on 2026-09-15), plus `ctrl+x v` for the
|
||||||
verbose transcript view (`app:toggleTranscript`, same keystroke as opencode's `<leader>v`; the
|
verbose transcript view (`app:toggleTranscript`, same keystroke as opencode's `<leader>v`; the
|
||||||
default `ctrl+o` still works, and `ctrl+e` inside the view expands everything).
|
default `ctrl+o` still works, and `ctrl+e` inside the view expands everything).
|
||||||
- `CLAUDE.md` — global user preferences applied to every project.
|
- `CLAUDE.md` — global user preferences applied to every project.
|
||||||
@@ -29,17 +34,32 @@ back into this repo.
|
|||||||
installed, and always exits 0.
|
installed, and always exits 0.
|
||||||
- `hooks/git-guard.py` — PreToolUse hook on `Bash`: structurally blocks anything that commits,
|
- `hooks/git-guard.py` — PreToolUse hook on `Bash`: structurally blocks anything that commits,
|
||||||
rewrites history or discards working-tree changes — `git commit` / `push` / `revert` /
|
rewrites history or discards working-tree changes — `git commit` / `push` / `revert` /
|
||||||
`cherry-pick` / `rebase` / `merge` (not `--abort`), `clean` / `reset` in any form that moves HEAD
|
`cherry-pick` / `rebase -i|--autostash` / `merge` (not `--abort`), `clean` / `reset` in any form that moves HEAD
|
||||||
or unstages everything (`reset -- <paths>`, `reset HEAD <path>` stay allowed) / `restore <paths>`
|
or unstages everything (`reset -- <paths>`, `reset HEAD <path>` stay allowed) / `restore <paths>`
|
||||||
(`--staged <path>` is fine, `--staged .` is not) / `checkout --` / `checkout .` / `stash drop|pop|clear` /
|
(`--staged <path>` is fine, `--staged .` is not) / `checkout --` / `checkout .` / `stash drop|pop|clear` /
|
||||||
`branch -D|-f|-M`, and `gh pr merge` / `create` / `close` / `edit` — wherever the flags sit
|
`branch -D|-f|-M`, and `gh pr merge` / `close`, `gh pr create` without `--head` (gh would push
|
||||||
|
the branch itself) and `gh pr edit` beyond `--title` / `--body` — wherever the flags sit
|
||||||
(`git -C . push`, `/usr/bin/git push`, `sh -c 'git push'`), which the prefix-matched deny rules
|
(`git -C . push`, `/usr/bin/git push`, `sh -c 'git push'`), which the prefix-matched deny rules
|
||||||
can't. The discard group is what protects parallel sessions sharing one checkout. Exit 2 + a
|
can't. A plain `rebase` and its `--continue` / `--abort` pass: Claude rebases and fixes
|
||||||
|
conflicts when asked (`CLAUDE.md`), and settings.json asks before each `git rebase`. The
|
||||||
|
discard group is what protects parallel sessions sharing one checkout. Exit 2 + a
|
||||||
one-line reason. Heredoc bodies are skipped on purpose (file contents). Regression matrix:
|
one-line reason. Heredoc bodies are skipped on purpose (file contents). Regression matrix:
|
||||||
`bash ~/.claude/hooks/git-guard-test.sh` (a file on purpose — typed inline, the commands under
|
`bash ~/.claude/hooks/git-guard-test.sh` (a file on purpose — typed inline, the commands under
|
||||||
test would trip the live hook).
|
test would trip the live hook). Repos listed in `~/.claude/git-guard-exempt` (host-local, not in
|
||||||
|
this repo — it names projects; one main checkout per line, worktrees included): `commit` and
|
||||||
|
`merge` pass there — judged from the hook input's
|
||||||
|
`cwd`, every `cd` before the call and `git -C` — except `commit --amend` / `-a`. A user-scope deny
|
||||||
|
cannot be lifted for one repo, so `git commit` is off the deny list: the hook is its only
|
||||||
|
structural block.
|
||||||
- `hooks/notify.py` — Notification hook (`idle_prompt|permission_prompt`): `notify-send` desktop
|
- `hooks/notify.py` — Notification hook (`idle_prompt|permission_prompt`): `notify-send` desktop
|
||||||
notification when Claude is waiting on you; no-op on a headless host.
|
notification when Claude is waiting on you; no-op on a headless host.
|
||||||
|
- `../tmux/scripts/agent-state.sh` — UserPromptSubmit (busy) / Notification (input) / Stop (done) /
|
||||||
|
StopFailure (error) / SessionEnd (off) hooks, all async: marks the tmux pane this session runs in
|
||||||
|
(`@agent` pane option, drawn as ✓ ? ! after the window name by tmux.conf; visiting the pane
|
||||||
|
greys it out, the next prompt removes it) and buzzes the phone for a new mark on a pane not in
|
||||||
|
view (termux-notification, else a bell). Claude sessions run in daemon-spawned workers with no
|
||||||
|
tmux env, so the script finds the pane by the session name Claude puts in the pane title. Shared with codex
|
||||||
|
(`~/.codex/hooks.json`, untracked), pi and opencode. No-op outside tmux.
|
||||||
- `hooks/drift-check.sh` — SessionStart + UserPromptSubmit hook, read-only: fails loudly when a
|
- `hooks/drift-check.sh` — SessionStart + UserPromptSubmit hook, read-only: fails loudly when a
|
||||||
`~/.config/claude/*` file has broken out of the repo into a plain file, or `~/.claude/*` no longer
|
`~/.config/claude/*` file has broken out of the repo into a plain file, or `~/.claude/*` no longer
|
||||||
resolves into the dots repo. That is what Claude Code's atomic settings rewrite does through the
|
resolves into the dots repo. That is what Claude Code's atomic settings rewrite does through the
|
||||||
@@ -55,14 +75,16 @@ back into this repo.
|
|||||||
- `skills/` — custom [Agent Skills](https://code.claude.com/docs/en/skills). See **Skills** below.
|
- `skills/` — custom [Agent Skills](https://code.claude.com/docs/en/skills). See **Skills** below.
|
||||||
- `link.sh` — idempotent bootstrap that creates the `~/.claude` symlinks below.
|
- `link.sh` — idempotent bootstrap that creates the `~/.claude` symlinks below.
|
||||||
- `../git/claude-commit-msg` + `../git/hooks/prepare-commit-msg` — the commit-message handoff.
|
- `../git/claude-commit-msg` + `../git/hooks/prepare-commit-msg` — the commit-message handoff.
|
||||||
A session pipes its suggestion into `claude-commit-msg save`, which stores it as its own file under
|
A session pipes its suggestion into `claude-commit-msg save` (run inside the target repo), which
|
||||||
`$(git rev-parse --git-path CLAUDE_COMMIT_MSG.d)/` **with the paths it staged**; on the next
|
stores it as its own file under `$(git rev-parse --git-path CLAUDE_COMMIT_MSG.d)/` **with the paths
|
||||||
`git commit` / lazygit `C` the hook prefills the editor with the message(s) whose paths are part of
|
it staged**; on the next `git commit` / lazygit `C` the hook prefills the editor with the message(s)
|
||||||
that commit and consumes them (`used/`). Parallel sessions therefore never overwrite each other,
|
whose paths are part of that commit and consumes them (`used/`). Parallel sessions therefore never
|
||||||
and committing one session's files gets that session's message. `claude-commit-msg list` shows
|
overwrite each other, and committing one session's files gets that session's message.
|
||||||
what is pending. The old single `CLAUDE_COMMIT_MSG` file is still honoured. Both tracked here
|
`claude-commit-msg list` shows what is pending. The old single `CLAUDE_COMMIT_MSG` file is still
|
||||||
(deployed to `~/.config/git/`); the hook is installed **per repo** — see setup step 4 — and
|
honoured. Both tracked here (deployed to `~/.config/git/`); the hook is installed **per repo** — see
|
||||||
deliberately not via a global `core.hooksPath`, which would override a project's own hooks.
|
setup step 4 — and deliberately not via a global `core.hooksPath`, which would override a project's
|
||||||
|
own hooks. The prefill only runs for an editor commit: lazygit's `c` panel passes the message to git
|
||||||
|
directly, so the hook skips and the suggestion stays pending.
|
||||||
|
|
||||||
The `settings.json` hook commands for the newer scripts are wrapped in `[ -f … ]`, so a host that
|
The `settings.json` hook commands for the newer scripts are wrapped in `[ -f … ]`, so a host that
|
||||||
has pulled the repo but not yet re-stowed just skips them instead of blocking every Bash call.
|
has pulled the repo but not yet re-stowed just skips them instead of blocking every Bash call.
|
||||||
@@ -93,7 +115,8 @@ claude
|
|||||||
```
|
```
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
# 4. In every repo you commit in: install the one-shot commit-message prefill hook
|
# 4. In every repo you commit in: install the commit-message prefill hook. New clones/inits get it
|
||||||
|
# from ~/.config/git/template (init.templateDir) once that template hook is a symlink to it too.
|
||||||
ln -sf ~/.config/git/hooks/prepare-commit-msg (git rev-parse --git-path hooks)/prepare-commit-msg
|
ln -sf ~/.config/git/hooks/prepare-commit-msg (git rev-parse --git-path hooks)/prepare-commit-msg
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -101,6 +124,12 @@ ln -sf ~/.config/git/hooks/prepare-commit-msg (git rev-parse --git-path hooks)/p
|
|||||||
need to run `link.sh` by hand (`bash ~/.config/claude/link.sh`) if a link later gets clobbered
|
need to run `link.sh` by hand (`bash ~/.config/claude/link.sh`) if a link later gets clobbered
|
||||||
(e.g. Claude's `/config` replaces one with a plain file) — it's idempotent and self-healing.
|
(e.g. Claude's `/config` replaces one with a plain file) — it's idempotent and self-healing.
|
||||||
|
|
||||||
|
Tracked *files* in `~/.claude` link straight at the repo copy, not through `~/.config/claude`:
|
||||||
|
Claude Code follows one symlink hop and then replaces the file atomically (temp + rename), so the
|
||||||
|
old two-hop chain lost its middle link to a plain file on every settings write (`/model`, effort,
|
||||||
|
plugins, `/config`) and the repo stopped seeing changes. With one hop the rename lands in the
|
||||||
|
checkout. Dirs (`hooks`, `skills`) still go through `~/.config/claude`.
|
||||||
|
|
||||||
A **new skill/hook** added to the repo shows up after a re-stow — `./install.sh` (or `dotsync`) links
|
A **new skill/hook** added to the repo shows up after a re-stow — `./install.sh` (or `dotsync`) links
|
||||||
the new file into `~/.config/claude`, which `link.sh` then surfaces into `~/.claude`.
|
the new file into `~/.config/claude`, which `link.sh` then surfaces into `~/.claude`.
|
||||||
|
|
||||||
@@ -122,8 +151,9 @@ so Claude never auto-triggers them. Run them inside the repo whose branch you're
|
|||||||
copy-paste markdown block, and copies it to the clipboard with `wl-copy`. Leaves verification
|
copy-paste markdown block, and copies it to the clipboard with `wl-copy`. Leaves verification
|
||||||
checkboxes for you; never creates/pushes the PR.
|
checkboxes for you; never creates/pushes the PR.
|
||||||
- **`/commit-msg [hint]`** — reads the **staged** diff, infers the repo's commit style from recent
|
- **`/commit-msg [hint]`** — reads the **staged** diff, infers the repo's commit style from recent
|
||||||
`git log` (e.g. this repo's `[Scope] summary`), drafts a matching message, prints it, and copies
|
`git log` (e.g. this repo's `[Scope] summary`), drafts a matching message, prints it, and saves
|
||||||
it to the clipboard with `wl-copy`. Never stages or commits — draft only.
|
it for the `prepare-commit-msg` hook (lazygit `C` prefills it). Never stages or commits — draft
|
||||||
|
only.
|
||||||
- **`/verbose [on|off|status]`** — flips the *persisted* verbose view for the current project:
|
- **`/verbose [on|off|status]`** — flips the *persisted* verbose view for the current project:
|
||||||
`viewMode` (`verbose` / `default` — the key that actually decides the view and beats a sticky
|
`viewMode` (`verbose` / `default` — the key that actually decides the view and beats a sticky
|
||||||
`/focus` choice for new sessions) plus `verbose` and `showThinkingSummaries`, written to the
|
`/focus` choice for new sessions) plus `verbose` and `showThinkingSummaries`, written to the
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ resize), trailing (lowest-priority) segments are dropped from the right until it
|
|||||||
Wide — everything:
|
Wide — everything:
|
||||||
|
|
||||||
```
|
```
|
||||||
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 🔢 ↑63M ↓595k │ ♻️ 91% 1h │ 💾 81%
|
📁 ~/.dots │ 🌿 .dots main* │ 💬 #42 3 · codex 2 │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 🔢 ↑63M ↓595k │ ⚡ 63 t/s │ ♻️ 91% 1h │ 💾 81%
|
||||||
```
|
```
|
||||||
|
|
||||||
Narrow — trailing segments trimmed:
|
Narrow — trailing segments trimmed:
|
||||||
|
|
||||||
```
|
```
|
||||||
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h
|
📁 ~/.dots │ 🌿 .dots main* │ 💬 #42 3 · codex 2 │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m
|
||||||
```
|
```
|
||||||
|
|
||||||
Every segment is defensive: if its data is missing or a command fails, the segment is
|
Every segment is defensive: if its data is missing or a command fails, the segment is
|
||||||
@@ -28,11 +28,13 @@ is supplementary to the main colored value.
|
|||||||
| | **Vim mode** | `[I]`/`[N]`/`[V]` editor mode | only with `editorMode: vim`; pair with `hideVimModeIndicator: true` so the built-in one doesn't duplicate it |
|
| | **Vim mode** | `[I]`/`[N]`/`[V]` editor mode | only with `editorMode: vim`; pair with `hideVimModeIndicator: true` so the built-in one doesn't duplicate it |
|
||||||
| 📁 | **Directory** | Current working dir, with `~` for home | cyan |
|
| 📁 | **Directory** | Current working dir, with `~` for home | cyan |
|
||||||
| 🌿 | **Git** | `repo branch` + `*` if dirty + `↑N`/`↓N` ahead/behind upstream | repo = magenta; branch = green when clean, yellow + red `*` when dirty; `↑` cyan, `↓` yellow. Omitted outside a repo |
|
| 🌿 | **Git** | `repo branch` + `*` if dirty + `↑N`/`↓N` ahead/behind upstream | repo = magenta; branch = green when clean, yellow + red `*` when dirty; `↑` cyan, `↓` yellow. Omitted outside a repo |
|
||||||
|
| 💬 | **PR review threads** | `#N` of the current branch's open PR + its **unresolved review threads** (any author, outdated ones included; `+` past 100), Codex's share broken out as `· codex N`; green `✓` when none are left | A thread counts for whoever opened it (its first comment's author; Codex = login matching `codex`, as in `/pr-loop`). PR-conversation comments are not counted — they have no resolved state, so a summary or an LGTM would never let it reach zero. A GitHub round trip is ~0.7 s, so a render only reads the per-branch cache `~/.claude/cache/statusline.pr.<hash>.json` and, once it is older than 90 s, spawns a detached `statusline.py --pr-refresh` that updates it (one at a time, via a `.lock` claim) — new counts show on the next render. A failed fetch (no GitHub remote — e.g. this repo on gitea —, gh not authed, offline) backs off 10 min and keeps the last counts with a gray `?`. Omitted with no `gh`, no open PR or a detached HEAD |
|
||||||
| 🤖 | **Model** | Active model display name + `· effort` level (`low`/`medium`/`high`/`xhigh`) | name = blue, effort = light gray; effort omitted for models without the param |
|
| 🤖 | **Model** | Active model display name + `· effort` level (`low`/`medium`/`high`/`xhigh`) | name = blue, effort = light gray; effort omitted for models without the param |
|
||||||
| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | usage comes from the payload's `context_window` (`used_percentage` + `current_usage`, Claude Code 2.1.x); the transcript's last `message.usage` is only a fallback. Window from `context_window.context_window_size`, then `(1M …)` in the display name, else 200k — **not** from `model.id`, which arrives with any `[1m]` suffix already stripped. Adds a red **⚠compact** at ≥80% |
|
| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | usage comes from the payload's `context_window` (`used_percentage` + `current_usage`, Claude Code 2.1.x); the transcript's last `message.usage` is only a fallback. Window from `context_window.context_window_size`, then `(1M …)` in the display name, else 200k — **not** from `model.id`, which arrives with any `[1m]` suffix already stripped. Adds a red **⚠compact** at ≥80% |
|
||||||
| 📊 | **5h usage** | `% of the 5-hour rolling limit used` + time until it resets | from `rate_limits.five_hour`; Pro/Max only, and absent until the first API response of a session |
|
| 📊 | **5h usage** | `% of the 5-hour rolling limit used` + time until it resets | from `rate_limits.five_hour`; Pro/Max only, and absent until the first API response of a session |
|
||||||
| 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s |
|
| 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s |
|
||||||
| 🔢 | **Session tokens** | `↑` input + `↓` output tokens used so far this session (cumulative, `k`/`M`) | summed over the transcript's assistant `message.usage` blocks, de-duplicated by message id (one line per content block shares the same usage). Input = `input + cache_creation + cache_read` — everything billed as input, so on a 1M session it dwarfs output (every call re-reads the cached prefix). Main transcript only: subagent usage lives in separate sidechain files. Incremental: a per-session state file `~/.claude/cache/statusline.tokens.<session>.json` remembers the byte offset already summed, so each refresh parses only the new tail (cold scan of a 6.5 MB transcript ≈ 70 ms, warm ≈ 0.1 ms); stale state files are pruned after 7 days once 50+ pile up. Omitted when there is no transcript; `↑` cyan, `↓` green, no thresholds |
|
| 🔢 | **Session tokens** | `↑` input + `↓` output tokens used so far this session (cumulative, `k`/`M`) | summed over the transcript's assistant `message.usage` blocks, de-duplicated by message id (one line per content block shares the same usage). Input = `input + cache_creation + cache_read` — everything billed as input, so on a 1M session it dwarfs output (every call re-reads the cached prefix). Main transcript only: subagent usage lives in separate sidechain files. Incremental: a per-session state file `~/.claude/cache/statusline.tokens.<session>.json` remembers the byte offset already summed, so each refresh parses only the new tail (warm ≈ 0.2 ms), at most 8 MiB per refresh (≈ 150 ms) — Claude Code kills a status script when the next refresh fires, so a cold scan of a huge resumed transcript is spread over a few refreshes (totals climb, then settle) instead of restarting from zero each time. Stale state files are pruned after 7 days once 50+ pile up. Omitted when there is no transcript; `↑` cyan, `↓` green, no thresholds |
|
||||||
|
| ⚡ | **Decode speed** | `N t/s` — tokens per second the model is generating | duration-weighted mean over the last 8 thinking blocks of the main conversation: `thinking_tokens / thinkingDurationMs` from the same transcript scan (blocks under 1 s or 50 tokens skipped). The only clean per-call timing Claude Code records: the payload's `cost.total_api_duration_ms` also absorbs subagent and advisor calls (it grew 28 s in a 12 s wall interval with no main-conversation completion) and its `output_tokens` is sometimes a mid-stream partial, so neither can give a rate. Includes first-token latency, so short blocks read a little low and long ones converge on the real decode speed (Fable 5.1 measured 60–85 t/s). Needs thinking enabled — omitted until a thinking block has completed, never shown with thinking off |
|
||||||
| ♻️ | **Prompt cache** | hit `%` (higher is better) + cache TTL (`5m` / `1h`), yellow `·cold` once it has expired | from the payload's `prompt_cache` (2.1.260+: `hit_ratio` 0–1, `ttl`, `warm`); computed from usage on older payloads. A `1h` TTL is what keeps a 10-min `/pr-loop` cadence warm |
|
| ♻️ | **Prompt cache** | hit `%` (higher is better) + cache TTL (`5m` / `1h`), yellow `·cold` once it has expired | from the payload's `prompt_cache` (2.1.260+: `hit_ratio` 0–1, `ttl`, `warm`); computed from usage on older payloads. A `1h` TTL is what keeps a 10-min `/pr-loop` cadence warm |
|
||||||
| 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` |
|
| 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` |
|
||||||
|
|
||||||
@@ -44,7 +46,9 @@ attention. Light gray = secondary detail. Thresholds (`green < … < yellow <
|
|||||||
| Segment | green | yellow | red |
|
| Segment | green | yellow | red |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Context | `< 50%` | `50–80%` | `≥ 80%` (⚠compact) |
|
| Context | `< 50%` | `50–80%` | `≥ 80%` (⚠compact) |
|
||||||
|
| PR review threads | `✓` none unresolved | thread count | `codex N` |
|
||||||
| 5h usage | `< 50%` | `50–80%` | `≥ 80%` |
|
| 5h usage | `< 50%` | `50–80%` | `≥ 80%` |
|
||||||
|
| Decode speed (inverted) | `≥ 40 t/s` | `20–40 t/s` | `< 20 t/s` |
|
||||||
| Prompt cache (inverted) | `≥ 80%` | `50–80%` | `< 50%` |
|
| Prompt cache (inverted) | `≥ 80%` | `50–80%` | `< 50%` |
|
||||||
| Disk | `< 75%` | `75–90%` | `≥ 90%` |
|
| Disk | `< 75%` | `75–90%` | `≥ 90%` |
|
||||||
|
|
||||||
@@ -74,5 +78,6 @@ events, so system metrics sat visibly stale between turns — the tmux status ba
|
|||||||
## Where it lives
|
## Where it lives
|
||||||
|
|
||||||
`statusline.py` is tracked in this repo and symlinked to `~/.claude/statusline.py`;
|
`statusline.py` is tracked in this repo and symlinked to `~/.claude/statusline.py`;
|
||||||
`settings.json` runs it via `statusLine` (`/usr/bin/env python3 ~/.claude/statusline.py`).
|
`settings.json` runs it via `statusLine` (`/usr/bin/env python3 ~/.claude/statusline.py`) on every
|
||||||
Edits take effect on the next status refresh.
|
message event and, through `refreshInterval: 60`, once a minute while the session idles — so the PR
|
||||||
|
count moves while you wait on a review. Edits take effect on the next status refresh.
|
||||||
@@ -1,12 +1,24 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Regression matrix for git-guard.py: `bash ~/.claude/hooks/git-guard-test.sh` (exit 1 on mismatch).
|
# Regression matrix for git-guard.py: `bash ~/.claude/hooks/git-guard-test.sh` (exit 1 on mismatch).
|
||||||
# Kept as a file on purpose: typed inline, the very commands under test would trip the live hook.
|
# Kept as a file on purpose: typed inline, the very commands under test would trip the live hook.
|
||||||
G="$(dirname "$(readlink -f "$0")")/git-guard.py"; fail=0
|
G="$(dirname "$(readlink -f "$0")")/git-guard.py"
|
||||||
|
fail=0
|
||||||
|
# No exempt repository for the rows that do not ask for one, whatever this host's ~/.claude/git-guard-exempt says.
|
||||||
|
export GIT_GUARD_EXEMPT=""
|
||||||
# `git reset <one arg>` is allowed only for an existing path: give the matrix one to point at.
|
# `git reset <one arg>` is allowed only for an existing path: give the matrix one to point at.
|
||||||
tmp=$(mktemp -d) && cd "$tmp" && touch existing.py
|
tmp=$(mktemp -d) && cd "$tmp" && touch existing.py
|
||||||
trap 'rm -rf "$tmp"' EXIT
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
run() { printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$2")}}" | python3 "$G" 2>/dev/null; rc=$?
|
run() {
|
||||||
got=allow; [ $rc -eq 2 ] && got=block; [ "$got" = "$1" ] && mark=ok || { mark=MISMATCH; fail=1; }; printf '%-8s %-6s %-6s %s\n' "$mark" "$1" "$got" "$(printf '%s' "$2" | head -1)"; }
|
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$2")}}" | python3 "$G" 2>/dev/null
|
||||||
|
rc=$?
|
||||||
|
got=allow
|
||||||
|
[ $rc -eq 2 ] && got=block
|
||||||
|
[ "$got" = "$1" ] && mark=ok || {
|
||||||
|
mark=MISMATCH
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
printf '%-8s %-6s %-6s %s\n' "$mark" "$1" "$got" "$(printf '%s' "$2" | head -1)"
|
||||||
|
}
|
||||||
echo "--- must block: commits / history ---"
|
echo "--- must block: commits / history ---"
|
||||||
run block 'git push'
|
run block 'git push'
|
||||||
run block 'git -C /home/coja/.dots push origin main'
|
run block 'git -C /home/coja/.dots push origin main'
|
||||||
@@ -28,7 +40,10 @@ run block 'git commit -m "fix: grep pattern; not a command"'
|
|||||||
run block 'git revert HEAD'
|
run block 'git revert HEAD'
|
||||||
run block 'git cherry-pick abc123'
|
run block 'git cherry-pick abc123'
|
||||||
run block 'git rebase -i HEAD~3'
|
run block 'git rebase -i HEAD~3'
|
||||||
run block 'git rebase main'
|
run block 'git rebase --interactive main'
|
||||||
|
run block 'git -C . rebase -i --autosquash main'
|
||||||
|
run block 'git rebase --autostash origin/main'
|
||||||
|
run block 'git -c rebase.autoStash=true rebase main'
|
||||||
run block 'git merge feature'
|
run block 'git merge feature'
|
||||||
run block 'git merge --no-ff feature'
|
run block 'git merge --no-ff feature'
|
||||||
echo "--- must block: discards working-tree changes ---"
|
echo "--- must block: discards working-tree changes ---"
|
||||||
@@ -63,11 +78,16 @@ run block 'git stash clear'
|
|||||||
run block 'git branch -D feature'
|
run block 'git branch -D feature'
|
||||||
run block 'git branch -f main HEAD~1'
|
run block 'git branch -f main HEAD~1'
|
||||||
run block 'git branch -M main trunk'
|
run block 'git branch -M main trunk'
|
||||||
echo "--- must block: gh pr write ops ---"
|
echo "--- must block: gh pr write ops (create only with --head, edit only title/body) ---"
|
||||||
run block 'gh pr merge 12 --squash'
|
run block 'gh pr merge 12 --squash'
|
||||||
run block 'gh -R o/r pr create --fill'
|
|
||||||
run block 'gh pr edit 3 --title x'
|
|
||||||
run block 'gh pr close 4'
|
run block 'gh pr close 4'
|
||||||
|
run block 'gh -R o/r pr create --fill'
|
||||||
|
run block 'gh pr create --title x --body y'
|
||||||
|
run block 'gh pr create --dry-run --fill'
|
||||||
|
run block 'gh pr edit 3 --add-reviewer bob'
|
||||||
|
run block 'gh pr edit 3 --title x --base main'
|
||||||
|
run block 'gh pr edit --add-label=bug 3'
|
||||||
|
run block 'gh pr edit 3 -b y -m v2'
|
||||||
echo "--- must allow ---"
|
echo "--- must allow ---"
|
||||||
run allow 'git status'
|
run allow 'git status'
|
||||||
run allow 'git log -1 --oneline'
|
run allow 'git log -1 --oneline'
|
||||||
@@ -88,6 +108,13 @@ run allow 'git checkout main'
|
|||||||
run allow 'git checkout -b feature'
|
run allow 'git checkout -b feature'
|
||||||
run allow 'git switch main'
|
run allow 'git switch main'
|
||||||
run allow 'git merge --abort'
|
run allow 'git merge --abort'
|
||||||
|
run allow 'git rebase main'
|
||||||
|
run allow 'git rebase origin/main'
|
||||||
|
run allow 'git rebase -Xtheirs main'
|
||||||
|
run allow 'git rebase --continue'
|
||||||
|
run allow 'git rebase --abort'
|
||||||
|
run allow 'git -c core.editor=true rebase --continue'
|
||||||
|
run allow 'GIT_EDITOR=true git rebase --continue'
|
||||||
run allow 'git stash'
|
run allow 'git stash'
|
||||||
run allow 'git stash list'
|
run allow 'git stash list'
|
||||||
run allow 'git stash push -m wip'
|
run allow 'git stash push -m wip'
|
||||||
@@ -110,12 +137,87 @@ run allow 'python3 - <<EOF
|
|||||||
print("git commit is manual")
|
print("git commit is manual")
|
||||||
EOF'
|
EOF'
|
||||||
run allow 'gh pr view 12 --json updatedAt'
|
run allow 'gh pr view 12 --json updatedAt'
|
||||||
|
run allow 'gh pr create --head feat --title x --body y'
|
||||||
|
run allow 'gh pr create -H feat --fill'
|
||||||
|
run allow 'gh -R o/r pr create --head=feat --fill'
|
||||||
|
run allow 'gh pr create --help'
|
||||||
|
run allow 'gh pr edit 3 --title x'
|
||||||
|
run allow 'gh pr edit 3 -t x -b "- item --base"'
|
||||||
|
run allow 'gh pr edit --body-file - 12'
|
||||||
|
run allow 'gh pr edit 12 -F body.md --repo o/r'
|
||||||
|
run allow 'gh pr edit 12 --title=x -bBody'
|
||||||
|
run allow 'gh workflow run ci.yml --ref main'
|
||||||
|
run allow 'gh run rerun 123 --failed'
|
||||||
run allow 'gh api graphql -f query="mutation{...}"'
|
run allow 'gh api graphql -f query="mutation{...}"'
|
||||||
run allow 'gh auth status'
|
run allow 'gh auth status'
|
||||||
run allow 'ls -la HEAD#x; git status'
|
run allow 'ls -la HEAD#x; git status'
|
||||||
run allow 'git log --grep="git push" -5'
|
run allow 'git log --grep="git push" -5'
|
||||||
run allow '~/.config/git/claude-commit-msg list'
|
run allow '~/.config/git/claude-commit-msg list'
|
||||||
|
echo "--- exempt repo (GIT_GUARD_EXEMPT = a scratch repo; commit and merge pass there, nothing else) ---"
|
||||||
|
# runin <expect> <cwd> <command>: the hook input carries the session's cwd, as Claude Code sends it.
|
||||||
|
R=$tmp/repo
|
||||||
|
WT=$tmp/repo-wt
|
||||||
|
git init -q "$R" && git -C "$R" -c user.name=t -c user.email=t@t commit -q --allow-empty -m init \
|
||||||
|
&& git -C "$R" worktree add -q "$WT" 2>/dev/null && mkdir -p "$R/sub" && git init -q "$tmp/other"
|
||||||
|
runin() {
|
||||||
|
printf '%s' "$(python3 -c 'import json,sys; print(json.dumps({"tool_name":"Bash","tool_input":{"command":sys.argv[1]},"cwd":sys.argv[2]}))' "$3" "$2")" | GIT_GUARD_EXEMPT=$R python3 "$G" 2>/dev/null
|
||||||
|
rc=$?
|
||||||
|
got=allow
|
||||||
|
[ $rc -eq 2 ] && got=block
|
||||||
|
[ "$got" = "$1" ] && mark=ok || {
|
||||||
|
mark=MISMATCH
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
printf '%-8s %-6s %-6s [%s] %s\n' "$mark" "$1" "$got" "${2#"$tmp"/}" "$(printf '%s' "$3" | head -1)"
|
||||||
|
}
|
||||||
|
runin allow "$R" 'git commit -m "docs: x"'
|
||||||
|
runin allow "$R" 'git commit -m x -- docs/usage.md'
|
||||||
|
runin allow "$R" 'git commit -m"fix a bug"'
|
||||||
|
runin allow "$R" 'git commit -m "-a is not the flag here"'
|
||||||
|
runin allow "$R" 'git commit --allow-empty -m x'
|
||||||
|
runin allow "$R/sub" 'git commit -m x'
|
||||||
|
runin allow "$R" 'git merge --ff-only feature'
|
||||||
|
runin allow "$R" 'git merge --no-ff feature'
|
||||||
|
runin allow "$WT" 'git commit -m x'
|
||||||
|
runin allow "$tmp" "git -C $WT commit -m x"
|
||||||
|
runin allow "$tmp" "cd $R && git commit -m x"
|
||||||
|
runin allow "$tmp" 'cd repo && git commit -m x'
|
||||||
|
runin allow "$tmp" "cd $WT; git status; git merge main"
|
||||||
|
runin allow "$R" "sh -c 'git commit -m x'"
|
||||||
|
runin block "$R" 'git push'
|
||||||
|
runin block "$R" 'git commit --amend --no-edit'
|
||||||
|
runin block "$R" 'git commit -am wip'
|
||||||
|
runin block "$R" 'git commit -qa -m wip'
|
||||||
|
runin block "$R" 'git commit --all -m wip'
|
||||||
|
runin block "$R" 'git rebase -i main'
|
||||||
|
runin block "$R" 'git cherry-pick abc123'
|
||||||
|
runin block "$R" 'git revert HEAD'
|
||||||
|
runin block "$R" 'git reset --hard HEAD~1'
|
||||||
|
runin block "$R" 'git checkout -- file.py'
|
||||||
|
runin block "$R" 'GIT_DIR=/x/.git git commit -m x'
|
||||||
|
runin block "$R" 'env GIT_WORK_TREE=/x git commit -m x'
|
||||||
|
runin block "$R" 'git --git-dir=/x/.git commit -m x'
|
||||||
|
runin block "$R" 'git -C /tmp commit -m x'
|
||||||
|
runin block "$R" 'git -C $(mktemp -d) commit -m x'
|
||||||
|
runin block "$tmp" "cd $R && cd /tmp && git commit -m x"
|
||||||
|
runin block "$R" 'cd - && git commit -m x'
|
||||||
|
runin block "$R" 'cd $HOME && git commit -m x'
|
||||||
|
runin block "$R" 'pushd /tmp && git commit -m x'
|
||||||
|
runin block "$tmp" 'git commit -m x'
|
||||||
|
runin block "$tmp/other" 'git commit -m x'
|
||||||
|
runin block "$tmp" "git -C $tmp/other merge x"
|
||||||
echo "--- fail-open ---"
|
echo "--- fail-open ---"
|
||||||
printf '%s' '{"tool_name":"Edit","tool_input":{"command":"git push"}}' | python3 "$G"; [ $? -eq 0 ] && echo "ok non-Bash tool ignored" || { echo "MISMATCH non-Bash"; fail=1; }
|
printf '%s' '{"tool_name":"Edit","tool_input":{"command":"git push"}}' | python3 "$G"
|
||||||
printf 'not json' | python3 "$G"; [ $? -eq 0 ] && echo "ok garbage stdin passes" || { echo "MISMATCH garbage"; fail=1; }
|
[ $? -eq 0 ] && echo "ok non-Bash tool ignored" || {
|
||||||
[ $fail -eq 0 ] && echo "GIT-GUARD MATRIX: all ok" || { echo "GIT-GUARD MATRIX: mismatches above"; exit 1; }
|
echo "MISMATCH non-Bash"
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
printf 'not json' | python3 "$G"
|
||||||
|
[ $? -eq 0 ] && echo "ok garbage stdin passes" || {
|
||||||
|
echo "MISMATCH garbage"
|
||||||
|
fail=1
|
||||||
|
}
|
||||||
|
[ $fail -eq 0 ] && echo "GIT-GUARD MATRIX: all ok" || {
|
||||||
|
echo "GIT-GUARD MATRIX: mismatches above"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -7,15 +7,24 @@ command text, so `git -C . push`, `git -c k=v commit`, `/usr/bin/git push`,
|
|||||||
This hook parses each shell segment instead: finds the git/gh token wherever it sits,
|
This hook parses each shell segment instead: finds the git/gh token wherever it sits,
|
||||||
skips git's global options (`-C dir`, `-c k=v`, `--git-dir=...`) and blocks
|
skips git's global options (`-C dir`, `-c k=v`, `--git-dir=...`) and blocks
|
||||||
|
|
||||||
git commit | push | revert | cherry-pick | rebase | merge (not --abort/--quit/--continue)
|
git commit | push | revert | cherry-pick | merge (not --abort/--quit/--continue)
|
||||||
|
git rebase -i / --interactive / --autostash
|
||||||
git clean | reset (any form that moves HEAD or unstages everything; `reset -- <paths>`,
|
git clean | reset (any form that moves HEAD or unstages everything; `reset -- <paths>`,
|
||||||
`reset HEAD <path>` and `reset <existing path>` are fine) | restore <paths> (--staged
|
`reset HEAD <path>` and `reset <existing path>` are fine) | restore <paths> (--staged
|
||||||
alone is fine, except `--staged .` / `:/`) | checkout -- / checkout . / -f
|
alone is fine, except `--staged .` / `:/`) | checkout -- / checkout . / -f
|
||||||
git stash drop | pop | clear git branch -D / -f / -M
|
git stash drop | pop | clear git branch -D / -f / -M
|
||||||
gh pr merge | create | close | edit
|
gh pr merge | close | create without --head | edit beyond --title/--body
|
||||||
|
|
||||||
i.e. anything that commits, rewrites history, or discards working-tree changes -- the last
|
i.e. anything that commits, rewrites history, or discards working-tree changes -- the last
|
||||||
group matters most with several sessions sharing one checkout. Mirrors pi's permission-gate.
|
group matters most with several sessions sharing one checkout. Mirrors pi's permission-gate.
|
||||||
|
On GitHub Claude may open a PR (settings.json asks first) and retitle / redescribe one, nothing
|
||||||
|
more: `gh pr create` must name its branch with --head, without which gh pushes it itself (even
|
||||||
|
under --dry-run), and `gh pr edit` must not touch reviewers, labels, base or the like.
|
||||||
|
A plain `git rebase` (and its --continue / --abort) passes: Claude rebases and fixes the
|
||||||
|
conflicts when the user asks (global CLAUDE.md), and settings.json asks before each one, as
|
||||||
|
pi's gate does. `-i` stays blocked (the squash/reword tool, and it needs an editor), and so
|
||||||
|
does --autostash: it stashes and re-pops other sessions' uncommitted edits, where a plain
|
||||||
|
rebase refuses to start on a dirty tree.
|
||||||
|
|
||||||
Exit 2 + one stderr line = the call is blocked and Claude reads the reason (stage and
|
Exit 2 + one stderr line = the call is blocked and Claude reads the reason (stage and
|
||||||
stop; /commit-msg drafts the message). Mirrors pi's permission-gate.ts.
|
stop; /commit-msg drafts the message). Mirrors pi's permission-gate.ts.
|
||||||
@@ -31,15 +40,27 @@ README that documents pushing must stay writable -- so a script piped in through
|
|||||||
is not inspected. Aliases (`git ci`) are not expanded. The deny list, the auto-mode
|
is not inspected. Aliases (`git ci`) are not expanded. The deny list, the auto-mode
|
||||||
classifier and the global CLAUDE.md still cover those; this is the belt for the prefix
|
classifier and the global CLAUDE.md still cover those; this is the belt for the prefix
|
||||||
rules' braces. Fails open: any parse error lets the command through (exit 0).
|
rules' braces. Fails open: any parse error lets the command through (exit 0).
|
||||||
|
|
||||||
|
Exempt repositories (one main checkout per line in ~/.claude/git-guard-exempt, host-local and
|
||||||
|
outside the dots repo on purpose: it names projects): there `git commit` and `git merge` pass
|
||||||
|
-- the user's decision for repos where Claude commits and merges itself. The repo is the one the call
|
||||||
|
actually targets: the hook input's `cwd`, moved by every `cd` before it in the command and by
|
||||||
|
`git -C`, resolved to its main checkout (so its worktrees count). Anything that hides the
|
||||||
|
target (`cd -`, `cd $VAR`, a substitution, `--git-dir`, `GIT_DIR=`) is judged not exempt.
|
||||||
|
`commit --amend` and `commit -a`/`--all` stay blocked there (they rewrite the last commit of
|
||||||
|
a shared checkout, or sweep other sessions' edits in), as do push, `rebase -i` and the discard
|
||||||
|
group. Known gap: a `cd` inside `( ... )` leaks into the commands after the parenthesis.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
GIT_BLOCK = {"commit", "push", "clean", "revert", "cherry-pick", "rebase"}
|
GIT_BLOCK = {"commit", "push", "clean", "revert", "cherry-pick"}
|
||||||
MERGE_OK = {"--abort", "--quit", "--continue"}
|
MERGE_OK = {"--abort", "--quit", "--continue"}
|
||||||
|
REBASE_BLOCK = {"-i", "--interactive", "--autostash"}
|
||||||
STASH_BLOCK = {"drop", "pop", "clear"}
|
STASH_BLOCK = {"drop", "pop", "clear"}
|
||||||
BRANCH_FORCE = {"-D", "-f", "--force", "-M"}
|
BRANCH_FORCE = {"-D", "-f", "--force", "-M"}
|
||||||
RESET_MODES = {"--hard", "--soft", "--merge", "--keep"}
|
RESET_MODES = {"--hard", "--soft", "--merge", "--keep"}
|
||||||
@@ -50,7 +71,20 @@ _HEX = re.compile(r"^[0-9a-f]{7,40}$")
|
|||||||
def looks_like_ref(a):
|
def looks_like_ref(a):
|
||||||
return (a in ("HEAD", "ORIG_HEAD", "FETCH_HEAD", "MERGE_HEAD") or a.startswith("refs/")
|
return (a in ("HEAD", "ORIG_HEAD", "FETCH_HEAD", "MERGE_HEAD") or a.startswith("refs/")
|
||||||
or "~" in a or "^" in a or "@{" in a or bool(_HEX.match(a)))
|
or "~" in a or "^" in a or "@{" in a or bool(_HEX.match(a)))
|
||||||
GH_PR_BLOCK = {"merge", "create", "close", "edit"}
|
GH_PR_BLOCK = {"merge", "close"}
|
||||||
|
# `gh pr edit` may only retitle and redescribe; these take their value as the next word (or as
|
||||||
|
# `--title=x` / `-tx`).
|
||||||
|
GH_EDIT_OK = {"-t", "--title", "-b", "--body", "-F", "--body-file", "-R", "--repo"}
|
||||||
|
# Repositories (main checkouts; their worktrees count) where Claude commits and merges itself:
|
||||||
|
# one path per line, `#` comments; a missing file exempts nothing. GIT_GUARD_EXEMPT
|
||||||
|
# (colon-separated) replaces the file for the test matrix; it is read from the hook's own
|
||||||
|
# environment (Claude Code's), which a prefix on the checked command cannot reach.
|
||||||
|
EXEMPT_FILE = "~/.claude/git-guard-exempt"
|
||||||
|
EXEMPT_VERBS = {"commit", "merge"}
|
||||||
|
# `git commit` options whose value is the next word (or, for the short ones, the rest of the word).
|
||||||
|
COMMIT_ARG_SHORT = set("mFCct")
|
||||||
|
COMMIT_ARG_LONG = {"--message", "--file", "--author", "--date", "--reuse-message", "--reedit-message",
|
||||||
|
"--fixup", "--squash", "--template", "--trailer", "--cleanup", "--pathspec-from-file"}
|
||||||
# global options that take a *separate* argument; the `--opt=value` spelling is one token.
|
# global options that take a *separate* argument; the `--opt=value` spelling is one token.
|
||||||
GIT_OPT_ARG = {"-C", "-c", "--git-dir", "--work-tree", "--exec-path", "--namespace",
|
GIT_OPT_ARG = {"-C", "-c", "--git-dir", "--work-tree", "--exec-path", "--namespace",
|
||||||
"--super-prefix", "--config-env"}
|
"--super-prefix", "--config-env"}
|
||||||
@@ -72,13 +106,102 @@ def _skip_opts(args, with_arg):
|
|||||||
return j
|
return j
|
||||||
|
|
||||||
|
|
||||||
def git_reason(args):
|
def exempt_repos():
|
||||||
|
env = os.environ.get("GIT_GUARD_EXEMPT")
|
||||||
|
if env is not None:
|
||||||
|
paths = env.split(":")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(os.path.expanduser(EXEMPT_FILE)) as f:
|
||||||
|
paths = [ln.split("#", 1)[0].strip() for ln in f]
|
||||||
|
except OSError:
|
||||||
|
return set()
|
||||||
|
return {os.path.realpath(os.path.expanduser(p)) for p in paths if p}
|
||||||
|
|
||||||
|
|
||||||
|
def main_checkout(d):
|
||||||
|
"""The main checkout of the repository `d` lies in (a worktree resolves to it), or None."""
|
||||||
|
try:
|
||||||
|
out = subprocess.run(["git", "-C", d, "rev-parse", "--path-format=absolute", "--git-common-dir"],
|
||||||
|
capture_output=True, text=True, timeout=3)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return None
|
||||||
|
common = out.stdout.strip().rstrip("/")
|
||||||
|
if out.returncode != 0 or not common:
|
||||||
|
return None
|
||||||
|
return os.path.realpath(os.path.dirname(common))
|
||||||
|
|
||||||
|
|
||||||
|
def in_exempt_repo(opts, cwd, pre):
|
||||||
|
"""Whether a git call with global options `opts`, run from `cwd` after the words `pre` of
|
||||||
|
its own command (`env X=1`, ...), targets an exempt repository."""
|
||||||
|
exempt = exempt_repos()
|
||||||
|
if not exempt or cwd is None or any(t.startswith(("GIT_DIR=", "GIT_WORK_TREE=", "GIT_COMMON_DIR="))
|
||||||
|
for t in pre):
|
||||||
|
return False
|
||||||
|
d, j = cwd, 0
|
||||||
|
while j < len(opts):
|
||||||
|
a = opts[j]
|
||||||
|
if a in ("--git-dir", "--work-tree") or a.startswith(("--git-dir=", "--work-tree=")):
|
||||||
|
return False
|
||||||
|
if a == "-C":
|
||||||
|
if j + 1 >= len(opts) or "SUBST" in opts[j + 1] or "$" in opts[j + 1]:
|
||||||
|
return False
|
||||||
|
d = os.path.join(d, os.path.expanduser(opts[j + 1]))
|
||||||
|
j += 2 if a in GIT_OPT_ARG else 1
|
||||||
|
return main_checkout(d) in exempt
|
||||||
|
|
||||||
|
|
||||||
|
def commit_rewrites_or_sweeps(rest):
|
||||||
|
"""`commit --amend` (rewrites the last commit) or `-a`/`--all` (takes in every modified
|
||||||
|
tracked file, other sessions' edits included)."""
|
||||||
|
skip = False
|
||||||
|
for a in rest:
|
||||||
|
if skip:
|
||||||
|
skip = False
|
||||||
|
continue
|
||||||
|
if a == "--":
|
||||||
|
break
|
||||||
|
if a in ("--amend", "--all"):
|
||||||
|
return True
|
||||||
|
if a in COMMIT_ARG_LONG:
|
||||||
|
skip = True
|
||||||
|
elif a.startswith("-") and not a.startswith("--"):
|
||||||
|
for k, ch in enumerate(a[1:], 1):
|
||||||
|
if ch == "a":
|
||||||
|
return True
|
||||||
|
if ch in COMMIT_ARG_SHORT:
|
||||||
|
skip = k == len(a) - 1 # the value is the next word
|
||||||
|
break
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def change_dir(cwd, args):
|
||||||
|
"""Where `cd args` leaves the shell started in `cwd`; None when that cannot be told."""
|
||||||
|
args = [a for a in args if a not in ("-L", "-P", "--")]
|
||||||
|
if not args:
|
||||||
|
return os.path.expanduser("~")
|
||||||
|
t = args[0]
|
||||||
|
if t == "-" or "SUBST" in t or "$" in t or (cwd is None and not t.startswith(("/", "~"))):
|
||||||
|
return None
|
||||||
|
return os.path.normpath(os.path.join(cwd or "/", os.path.expanduser(t)))
|
||||||
|
|
||||||
|
|
||||||
|
def git_reason(args, cwd=None, pre=()):
|
||||||
j = _skip_opts(args, GIT_OPT_ARG)
|
j = _skip_opts(args, GIT_OPT_ARG)
|
||||||
if j >= len(args):
|
if j >= len(args):
|
||||||
return None
|
return None
|
||||||
sub, rest = args[j], args[j + 1:]
|
sub, rest = args[j], args[j + 1:]
|
||||||
|
if (sub in EXEMPT_VERBS and not (sub == "commit" and commit_rewrites_or_sweeps(rest))
|
||||||
|
and in_exempt_repo(args[:j], cwd, pre)):
|
||||||
|
return None
|
||||||
if sub in GIT_BLOCK:
|
if sub in GIT_BLOCK:
|
||||||
return f"git {sub}"
|
return f"git {sub}"
|
||||||
|
if sub == "rebase":
|
||||||
|
flag = next((a for a in rest if a in REBASE_BLOCK), None)
|
||||||
|
if flag == "--autostash" or any(o.lower().startswith("rebase.autostash") for o in args[:j]):
|
||||||
|
return "git rebase --autostash (stashes other sessions' uncommitted edits)"
|
||||||
|
return f"git rebase {flag} (interactive history rewrite)" if flag else None
|
||||||
if sub == "reset":
|
if sub == "reset":
|
||||||
opts = [a for a in rest if a.startswith("-") and a != "--"]
|
opts = [a for a in rest if a.startswith("-") and a != "--"]
|
||||||
if RESET_MODES & set(opts):
|
if RESET_MODES & set(opts):
|
||||||
@@ -109,14 +232,41 @@ def git_reason(args):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def gh_edit_extra(rest):
|
||||||
|
"""The first `gh pr edit` option that is neither the title nor the body, else None."""
|
||||||
|
k = 0
|
||||||
|
while k < len(rest):
|
||||||
|
a = rest[k]
|
||||||
|
if a == "--":
|
||||||
|
break
|
||||||
|
name = a.split("=", 1)[0]
|
||||||
|
if name in GH_EDIT_OK:
|
||||||
|
k += 2 if name == a else 1 # skip the value, which may well start with `-`
|
||||||
|
continue
|
||||||
|
if a.startswith("-") and not (a[:2] in GH_EDIT_OK and not a.startswith("--")):
|
||||||
|
return a
|
||||||
|
k += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def gh_reason(args):
|
def gh_reason(args):
|
||||||
j = _skip_opts(args, GH_OPT_ARG)
|
j = _skip_opts(args, GH_OPT_ARG)
|
||||||
if j >= len(args) or args[j] != "pr":
|
if j >= len(args) or args[j] != "pr":
|
||||||
return None
|
return None
|
||||||
rest = args[j + 1:]
|
rest = args[j + 1:]
|
||||||
k = _skip_opts(rest, GH_OPT_ARG)
|
k = _skip_opts(rest, GH_OPT_ARG)
|
||||||
if k < len(rest) and rest[k] in GH_PR_BLOCK:
|
if k >= len(rest) or "--help" in rest:
|
||||||
return f"gh pr {rest[k]}"
|
return None
|
||||||
|
sub, rest = rest[k], rest[k + 1:]
|
||||||
|
if sub in GH_PR_BLOCK:
|
||||||
|
return f"gh pr {sub}"
|
||||||
|
if sub == "create" and not any(a in ("-H", "--head") or a.startswith("--head=")
|
||||||
|
or (a.startswith("-H") and not a.startswith("--")) for a in rest):
|
||||||
|
return "gh pr create without --head: gh would push the branch itself"
|
||||||
|
if sub == "edit":
|
||||||
|
extra = gh_edit_extra(rest)
|
||||||
|
if extra:
|
||||||
|
return f"gh pr edit {extra.split('=', 1)[0]}: only --title / --body edits are allowed"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -146,8 +296,8 @@ def segments(toks):
|
|||||||
yield seg
|
yield seg
|
||||||
|
|
||||||
|
|
||||||
def scan(text, depth=0):
|
def scan(text, depth=0, cwd=None):
|
||||||
"""Reason string for the first blocked invocation in `text`, else None."""
|
"""Reason string for the first blocked invocation in `text` (run from `cwd`), else None."""
|
||||||
if depth > 4:
|
if depth > 4:
|
||||||
return None
|
return None
|
||||||
text = _HEREDOC.sub(" ", text)
|
text = _HEREDOC.sub(" ", text)
|
||||||
@@ -163,23 +313,29 @@ def scan(text, depth=0):
|
|||||||
while prev != text:
|
while prev != text:
|
||||||
prev, text = text, _SUBST.sub(_grab, text)
|
prev, text = text, _SUBST.sub(_grab, text)
|
||||||
for body in bodies:
|
for body in bodies:
|
||||||
r = scan(body, depth + 1)
|
r = scan(body, depth + 1, cwd)
|
||||||
if r:
|
if r:
|
||||||
return r
|
return r
|
||||||
for toks in segments(tokenize(text)):
|
for toks in segments(tokenize(text)):
|
||||||
|
if toks[0] == "cd":
|
||||||
|
cwd = change_dir(cwd, toks[1:])
|
||||||
|
continue
|
||||||
|
if toks[0] in ("pushd", "popd"):
|
||||||
|
cwd = None
|
||||||
|
continue
|
||||||
for i, tok in enumerate(toks):
|
for i, tok in enumerate(toks):
|
||||||
base = os.path.basename(tok)
|
base = os.path.basename(tok)
|
||||||
r = None
|
r = None
|
||||||
if base == "git":
|
if base == "git":
|
||||||
r = git_reason(toks[i + 1:])
|
r = git_reason(toks[i + 1:], cwd, toks[:i])
|
||||||
elif base == "gh":
|
elif base == "gh":
|
||||||
r = gh_reason(toks[i + 1:])
|
r = gh_reason(toks[i + 1:])
|
||||||
elif base == "eval":
|
elif base == "eval":
|
||||||
r = scan(" ".join(toks[i + 1:]), depth + 1)
|
r = scan(" ".join(toks[i + 1:]), depth + 1, cwd)
|
||||||
elif base in SHELLS and "-c" in toks[i + 1:]:
|
elif base in SHELLS and "-c" in toks[i + 1:]:
|
||||||
k = toks.index("-c", i + 1) # `sh -c '<cmd>'`: the real command is one word
|
k = toks.index("-c", i + 1) # `sh -c '<cmd>'`: the real command is one word
|
||||||
if k + 1 < len(toks):
|
if k + 1 < len(toks):
|
||||||
r = scan(toks[k + 1], depth + 1)
|
r = scan(toks[k + 1], depth + 1, cwd)
|
||||||
if r:
|
if r:
|
||||||
return r
|
return r
|
||||||
return None
|
return None
|
||||||
@@ -191,16 +347,21 @@ def main():
|
|||||||
if data.get("tool_name", "Bash") != "Bash":
|
if data.get("tool_name", "Bash") != "Bash":
|
||||||
return
|
return
|
||||||
cmd = (data.get("tool_input") or {}).get("command") or ""
|
cmd = (data.get("tool_input") or {}).get("command") or ""
|
||||||
reason = scan(cmd) if cmd else None
|
reason = scan(cmd, 0, data.get("cwd") or os.getcwd()) if cmd else None
|
||||||
except Exception:
|
except Exception:
|
||||||
return # fail open: the deny list still applies
|
return # fail open: the deny list still applies
|
||||||
if reason:
|
if reason:
|
||||||
short = cmd.strip().splitlines()[0]
|
short = cmd.strip().splitlines()[0]
|
||||||
short = short if len(short) <= 80 else short[:77] + "..."
|
short = short if len(short) <= 80 else short[:77] + "..."
|
||||||
sys.stderr.write(
|
hint = ("On a PR Claude reads, opens one when asked (with --head <pushed branch>) and edits its "
|
||||||
f"git-guard: blocked `{short}` ({reason}). The user commits, pushes and merges "
|
"--title / --body; pushing, merging, closing and every other edit are the user's."
|
||||||
"manually (global CLAUDE.md): stage and stop, and draft the message with /commit-msg.\n"
|
if reason.startswith("gh ") else
|
||||||
)
|
"Rebase with a plain `git rebase` on a clean tree, and only when the user asked; if the "
|
||||||
|
"tree holds other sessions' edits, tell the user instead of stashing them."
|
||||||
|
if reason.startswith("git rebase") else
|
||||||
|
"The user commits, pushes and merges manually (global CLAUDE.md): stage and stop, "
|
||||||
|
"and draft the message with /commit-msg.")
|
||||||
|
sys.stderr.write(f"git-guard: blocked `{short}` ({reason}). {hint}\n")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
"context": "Global",
|
"context": "Global",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt+j": "scroll:lineDown",
|
"alt+j": "scroll:lineDown",
|
||||||
"alt+k": "scroll:lineUp"
|
"alt+k": "scroll:lineUp",
|
||||||
|
"ctrl+shift+j": "scroll:lineDown",
|
||||||
|
"ctrl+shift+k": "scroll:lineUp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,16 @@ items="settings.json statusline.py keybindings.json CLAUDE.md hooks skills"
|
|||||||
# Expose each item in ~/.claude (where Claude actually reads).
|
# Expose each item in ~/.claude (where Claude actually reads).
|
||||||
for item in $items; do
|
for item in $items; do
|
||||||
[ -e "$SRC/$item" ] || continue
|
[ -e "$SRC/$item" ] || continue
|
||||||
|
# Files link straight at the repo copy, not through ~/.config/claude: Claude Code
|
||||||
|
# resolves ONE symlink hop, then writes atomically (temp + rename) at that path —
|
||||||
|
# verified on 2.1.296 with `claude plugin disable` against a scratch CLAUDE_CONFIG_DIR.
|
||||||
|
# Through a ~/.claude -> ~/.config/claude -> repo chain, every settings write (/model,
|
||||||
|
# effort, plugins, /config) swapped the middle link for a plain file and severed the
|
||||||
|
# repo. One hop puts the rename inside the checkout, so both links survive. A SRC file
|
||||||
|
# that already drifted into a plain file keeps being the target (it may hold the newest
|
||||||
|
# edits) until the drift guard below is reconciled. Dirs keep the stow path.
|
||||||
|
target="$SRC/$item"
|
||||||
|
[ -f "$repo/$item" ] && [ -L "$SRC/$item" ] && target="$repo/$item"
|
||||||
if [ -d "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
|
if [ -d "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
|
||||||
echo "skip ~/.claude/$item (real directory in the way — move it aside first)" >&2
|
echo "skip ~/.claude/$item (real directory in the way — move it aside first)" >&2
|
||||||
continue
|
continue
|
||||||
@@ -62,14 +72,14 @@ for item in $items; do
|
|||||||
if [ "$same" != 1 ]; then
|
if [ "$same" != 1 ]; then
|
||||||
echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2
|
echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2
|
||||||
echo " it may hold newer edits. Review, then re-link:" >&2
|
echo " it may hold newer edits. Review, then re-link:" >&2
|
||||||
echo " diff '$DST/$item' '$SRC/$item' # compare; keep whichever is right" >&2
|
echo " diff '$DST/$item' '$target' # compare; keep whichever is right" >&2
|
||||||
echo " ln -sfn '$SRC/$item' '$DST/$item' # re-link" >&2
|
echo " ln -sfn '$target' '$DST/$item' # re-link" >&2
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
echo "healed ~/.claude/$item (plain copy identical to repo — re-linking)"
|
echo "healed ~/.claude/$item (plain copy identical to repo — re-linking)"
|
||||||
fi
|
fi
|
||||||
ln -sfn "$SRC/$item" "$DST/$item"
|
ln -sfn "$target" "$DST/$item"
|
||||||
echo "linked ~/.claude/$item -> ~/.config/claude/$item"
|
echo "linked ~/.claude/$item -> ${target/#$HOME/\~}"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Stow-layer drift guard: each SRC *file* should be a symlink into the repo, so edits
|
# Stow-layer drift guard: each SRC *file* should be a symlink into the repo, so edits
|
||||||
|
|||||||
@@ -4,23 +4,60 @@
|
|||||||
"pr": ""
|
"pr": ""
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cat:*)",
|
||||||
|
"Bash(head:*)",
|
||||||
|
"Bash(tail:*)",
|
||||||
|
"Bash(sed -n:*)",
|
||||||
|
"Bash(grep:*)",
|
||||||
|
"Bash(rg:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(wc:*)",
|
||||||
|
"Bash(git status:*)",
|
||||||
|
"Bash(git diff:*)",
|
||||||
|
"Bash(git log:*)",
|
||||||
|
"Bash(git show:*)",
|
||||||
|
"Bash(git rev-parse:*)",
|
||||||
|
"Bash(git branch:*)",
|
||||||
|
"Bash(git stash list:*)",
|
||||||
|
"Bash(pnpm run:*)",
|
||||||
|
"Bash(pnpm exec:*)",
|
||||||
|
"Bash(node -p:*)",
|
||||||
|
"Bash(gh pr view:*)",
|
||||||
|
"Bash(gh pr list:*)",
|
||||||
|
"Bash(gh pr diff:*)",
|
||||||
|
"Bash(gh pr checks:*)",
|
||||||
|
"Bash(gh pr status:*)",
|
||||||
|
"Bash(gh issue view:*)",
|
||||||
|
"Bash(gh issue list:*)",
|
||||||
|
"Bash(gh issue status:*)",
|
||||||
|
"Bash(gh run view:*)",
|
||||||
|
"Bash(gh run list:*)",
|
||||||
|
"Bash(gh run watch:*)",
|
||||||
|
"Bash(gh workflow view:*)",
|
||||||
|
"Bash(gh workflow list:*)",
|
||||||
|
"Bash(gh repo view:*)",
|
||||||
|
"Bash(gh release view:*)",
|
||||||
|
"Bash(gh release list:*)",
|
||||||
|
"Bash(gh search:*)",
|
||||||
|
"Bash(gh label list:*)",
|
||||||
|
"Bash(gh status:*)",
|
||||||
|
"Bash(gh auth status:*)",
|
||||||
|
"Bash(gh pr edit:*)",
|
||||||
|
"Bash(gh workflow run:*)",
|
||||||
|
"Bash(gh run rerun:*)"
|
||||||
|
],
|
||||||
"deny": [
|
"deny": [
|
||||||
"Bash(git push)",
|
"Bash(git push)",
|
||||||
"Bash(git push:*)",
|
"Bash(git push:*)",
|
||||||
"Bash(git commit)",
|
|
||||||
"Bash(git commit:*)",
|
|
||||||
"Bash(git reset --hard)",
|
"Bash(git reset --hard)",
|
||||||
"Bash(git reset --hard:*)",
|
"Bash(git reset --hard:*)",
|
||||||
"Bash(git clean)",
|
"Bash(git clean)",
|
||||||
"Bash(git clean:*)",
|
"Bash(git clean:*)",
|
||||||
"Bash(gh pr merge)",
|
"Bash(gh pr merge)",
|
||||||
"Bash(gh pr merge:*)",
|
"Bash(gh pr merge:*)",
|
||||||
"Bash(gh pr create)",
|
|
||||||
"Bash(gh pr create:*)",
|
|
||||||
"Bash(gh pr close)",
|
"Bash(gh pr close)",
|
||||||
"Bash(gh pr close:*)",
|
"Bash(gh pr close:*)",
|
||||||
"Bash(gh pr edit)",
|
|
||||||
"Bash(gh pr edit:*)",
|
|
||||||
"Read(**/.env)",
|
"Read(**/.env)",
|
||||||
"Read(**/.env.*)",
|
"Read(**/.env.*)",
|
||||||
"Read(**/*.env)",
|
"Read(**/*.env)",
|
||||||
@@ -42,9 +79,15 @@
|
|||||||
"Read(**/.gnupg/**)",
|
"Read(**/.gnupg/**)",
|
||||||
"Read(**/fish_variables)"
|
"Read(**/fish_variables)"
|
||||||
],
|
],
|
||||||
|
"ask": [
|
||||||
|
"Bash(gh pr create)",
|
||||||
|
"Bash(gh pr create:*)",
|
||||||
|
"Bash(git rebase)",
|
||||||
|
"Bash(git rebase:*)"
|
||||||
|
],
|
||||||
"defaultMode": "auto"
|
"defaultMode": "auto"
|
||||||
},
|
},
|
||||||
"model": "claude-fable-5-1[1m]",
|
"model": "opus",
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
@@ -67,6 +110,16 @@
|
|||||||
"timeout": 5
|
"timeout": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "f=\"$HOME/.config/tmux/scripts/agent-state.sh\"; [ -x \"$f\" ] && \"$f\" cc busy; exit 0",
|
||||||
|
"timeout": 5,
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"UserPromptExpansion": [
|
"UserPromptExpansion": [
|
||||||
@@ -125,6 +178,51 @@
|
|||||||
"timeout": 5
|
"timeout": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "idle_prompt|permission_prompt",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "f=\"$HOME/.config/tmux/scripts/agent-state.sh\"; [ -x \"$f\" ] && \"$f\" cc input; exit 0",
|
||||||
|
"timeout": 5,
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "f=\"$HOME/.config/tmux/scripts/agent-state.sh\"; [ -x \"$f\" ] && \"$f\" cc done; exit 0",
|
||||||
|
"timeout": 5,
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"StopFailure": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "f=\"$HOME/.config/tmux/scripts/agent-state.sh\"; [ -x \"$f\" ] && \"$f\" cc error; exit 0",
|
||||||
|
"timeout": 5,
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SessionEnd": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "f=\"$HOME/.config/tmux/scripts/agent-state.sh\"; [ -x \"$f\" ] && \"$f\" cc off; exit 0"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -136,13 +234,23 @@
|
|||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "/usr/bin/env python3 ~/.claude/statusline.py",
|
"command": "/usr/bin/env python3 ~/.claude/statusline.py",
|
||||||
"padding": 0,
|
"padding": 0,
|
||||||
|
"refreshInterval": 60,
|
||||||
"hideVimModeIndicator": true
|
"hideVimModeIndicator": true
|
||||||
},
|
},
|
||||||
"enabledPlugins": {
|
"enabledPlugins": {
|
||||||
"typescript-lsp@claude-plugins-official": true,
|
"typescript-lsp@claude-plugins-official": true,
|
||||||
"frontend-design@claude-plugins-official": true
|
"frontend-design@claude-plugins-official": true,
|
||||||
|
"rust-analyzer-lsp@claude-plugins-official": true
|
||||||
},
|
},
|
||||||
"effortLevel": "xhigh",
|
"effortLevel": "xhigh",
|
||||||
|
"modelSettings": {
|
||||||
|
"claude-fable-5-1": {
|
||||||
|
"effortLevel": "xhigh"
|
||||||
|
},
|
||||||
|
"claude-opus-5-5": {
|
||||||
|
"effortLevel": "xhigh"
|
||||||
|
}
|
||||||
|
},
|
||||||
"advisorModel": "fable",
|
"advisorModel": "fable",
|
||||||
"tui": "fullscreen",
|
"tui": "fullscreen",
|
||||||
"skipWorkflowUsageWarning": true,
|
"skipWorkflowUsageWarning": true,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
description: Draft a commit message for the staged changes, matching this repo's commit style, then print it in the reply, save it to the repo's CLAUDE_COMMIT_MSG file, and copy it to the clipboard. Never stages or commits. Use once you've staged what you want to commit.
|
description: Draft a commit message for the staged changes, matching this repo's commit style, then print it in the reply and save it to the repo's CLAUDE_COMMIT_MSG file. Never stages or commits. Use once you've staged what you want to commit.
|
||||||
argument-hint: [optional focus/scope hint]
|
argument-hint: [optional focus/scope hint]
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git rev-parse:*), Bash(wl-copy:*), Bash(~/.config/git/claude-commit-msg:*), Read, Glob
|
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git rev-parse:*), Bash(~/.config/git/claude-commit-msg:*), Read, Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# Draft a commit message for the staged changes
|
# Draft a commit message for the staged changes
|
||||||
@@ -26,9 +26,8 @@ Context for the current commit — what's staged, plus this repo's recent style:
|
|||||||
- Add a short body (wrapped ~72 cols) only when the change needs the *why*; otherwise
|
- Add a short body (wrapped ~72 cols) only when the change needs the *why*; otherwise
|
||||||
subject-only. Don't invent motivation that isn't evident from the diff.
|
subject-only. Don't invent motivation that isn't evident from the diff.
|
||||||
4. **Print the message in your reply first** — as a single fenced ` ```text ` block, copy-paste
|
4. **Print the message in your reply first** — as a single fenced ` ```text ` block, copy-paste
|
||||||
ready. This in-reply copy is the durable one: it lives in the transcript, so it survives even if
|
ready. This in-reply copy is the durable one: it lives in the transcript, so it survives even
|
||||||
the clipboard gets overwritten later (by me copying something else, or another Claude session
|
after the hook has consumed the handoff. Always print it.
|
||||||
running `wl-copy`). Always print it; don't rely on the clipboard alone.
|
|
||||||
5. **Hand it off** so I can recover it any time — feed the raw message to the CLI as a quoted
|
5. **Hand it off** so I can recover it any time — feed the raw message to the CLI as a quoted
|
||||||
heredoc (so quotes, backticks and `$` in the body survive untouched):
|
heredoc (so quotes, backticks and `$` in the body survive untouched):
|
||||||
```sh
|
```sh
|
||||||
@@ -41,8 +40,6 @@ Context for the current commit — what's staged, plus this repo's recent style:
|
|||||||
`prepare-commit-msg` hook prefills the next `git commit` / lazygit `C` with the message(s) whose
|
`prepare-commit-msg` hook prefills the next `git commit` / lazygit `C` with the message(s) whose
|
||||||
paths are part of that commit, then consumes them. Never write `CLAUDE_COMMIT_MSG` or
|
paths are part of that commit, then consumes them. Never write `CLAUDE_COMMIT_MSG` or
|
||||||
`COMMIT_EDITMSG` directly. The `save` output names the saved id and file path.
|
`COMMIT_EDITMSG` directly. The `save` output names the saved id and file path.
|
||||||
6. **Also copy it to the clipboard** as a convenience: `wl-copy < <file path printed by save>`.
|
Then tell me both places it is: **printed above** and **saved for the hook** (with its id).
|
||||||
Then tell me all three places it is: **printed above**, **saved for the hook** (with its id),
|
6. **Never** run `git add`, `git commit`, or `git push`, and never offer to — I stage and commit
|
||||||
and **on the clipboard** — so I never lose it to a stray copy.
|
|
||||||
7. **Never** run `git add`, `git commit`, or `git push`, and never offer to — I stage and commit
|
|
||||||
manually. Draft only.
|
manually. Draft only.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
description: Babysit the current branch's PR — address review comments that don't need my input, re-check every ~10 min, and stop when there are no open comments left or the automated reviewers (Codex, Claude) hit their limit. Prep-only — stages fixes and drafts the commit message; never commits, pushes, or merges.
|
description: Babysit the current branch's PR — address review comments that don't need my input, re-check every ~10 min, and stop when there are no open comments left or the automated reviewers (Codex, Claude) hit their limit. Prep-only — stages fixes and drafts the commit message; never commits, pushes, or merges.
|
||||||
argument-hint: [pr number or url — optional; defaults to the current branch's PR]
|
argument-hint: [pr number or url — optional; defaults to the current branch's PR]
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
allowed-tools: Bash(gh:*), Bash(git status:*), Bash(git diff:*), Bash(git log:*), Bash(git rev-parse:*), Bash(git fetch:*), Bash(git add:*), Bash(wl-copy:*), Bash(~/.config/git/claude-commit-msg:*), Read, Edit, Write, Grep, Glob, ScheduleWakeup
|
allowed-tools: Bash(gh:*), Bash(git status:*), Bash(git diff:*), Bash(git log:*), Bash(git rev-parse:*), Bash(git fetch:*), Bash(git add:*), Bash(~/.config/git/claude-commit-msg:*), Read, Edit, Write, Grep, Glob, ScheduleWakeup
|
||||||
---
|
---
|
||||||
|
|
||||||
# Watch this PR and resolve review comments (prep-only)
|
# Watch this PR and resolve review comments (prep-only)
|
||||||
@@ -238,8 +238,7 @@ Current state (auth + the current branch's PR):
|
|||||||
quoted heredoc, so quotes and backticks survive) — it is stored with the paths you staged this cycle, so the `prepare-commit-msg` hook prefills it only
|
quoted heredoc, so quotes and backticks survive) — it is stored with the paths you staged this cycle, so the `prepare-commit-msg` hook prefills it only
|
||||||
for the commit that contains them (other sessions' handoffs are not overwritten). Never write
|
for the commit that contains them (other sessions' handoffs are not overwritten). Never write
|
||||||
`CLAUDE_COMMIT_MSG` / `COMMIT_EDITMSG` directly.
|
`CLAUDE_COMMIT_MSG` / `COMMIT_EDITMSG` directly.
|
||||||
- **(c) Copy it:** `wl-copy < <file path printed by save>`.
|
- Tell me both locations. **Never** run `git commit`.
|
||||||
- Tell me all three locations. **Never** run `git commit`.
|
|
||||||
(Skip this step when nothing new was staged this cycle.)
|
(Skip this step when nothing new was staged this cycle.)
|
||||||
|
|
||||||
10. **Ping + schedule.** Give me a tight summary — deltas only, don't restate unchanged threads:
|
10. **Ping + schedule.** Give me a tight summary — deltas only, don't restate unchanged threads:
|
||||||
@@ -271,9 +270,10 @@ Current state (auth + the current branch's PR):
|
|||||||
|
|
||||||
- **Never** `git commit`, `git push`, `gh pr merge`, `gh pr create`, `gh pr close`, or `gh pr edit`
|
- **Never** `git commit`, `git push`, `gh pr merge`, `gh pr create`, `gh pr close`, or `gh pr edit`
|
||||||
— I do all of those. You stage, react, resolve, and draft; nothing more. The only comment you may
|
— I do all of those. You stage, react, resolve, and draft; nothing more. The only comment you may
|
||||||
post is a needs-input decision question. This is enforced, not just asked: `settings.json` denies
|
post is a needs-input decision question. Commit, push, merge and close are enforced, not just
|
||||||
those commands and the `git-guard.py` PreToolUse hook blocks any spelling of them, so a slip is
|
asked: `settings.json` denies them and the `git-guard.py` PreToolUse hook blocks any spelling of
|
||||||
refused with a reason rather than executed.
|
them. `gh pr create` / `edit` are allowed globally (create asks first), so for this loop the rule
|
||||||
|
above is the only thing holding them back.
|
||||||
- Act only on the target PR. Don't touch unrelated files or other PRs.
|
- Act only on the target PR. Don't touch unrelated files or other PRs.
|
||||||
- **Keep context lean:** no raw JSON dumps in replies, no re-listing threads that haven't changed,
|
- **Keep context lean:** no raw JSON dumps in replies, no re-listing threads that haven't changed,
|
||||||
idle cycles ≤2 lines. Every line you emit is re-read (uncached) on every later cycle.
|
idle cycles ≤2 lines. Every line you emit is re-read (uncached) on every later cycle.
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
"""Claude Code status line.
|
"""Claude Code status line.
|
||||||
|
|
||||||
One line: vim mode | dir (repo-relative, hidden at the root) |
|
One line: vim mode | dir (repo-relative, hidden at the root) |
|
||||||
git(repo/branch +dirty +ahead/behind) | model (+effort) |
|
git(repo/branch +dirty +ahead/behind) | PR review threads (open count, Codex's share) | model (+effort) |
|
||||||
context% (+compact warn) | 5h usage (+reset eta) | cost (+burn rate) |
|
context% (+compact warn) | 5h usage (+reset eta) | cost (+burn rate) |
|
||||||
session tokens (cumulative in/out) | prompt-cache hit% (+ttl, cold marker) | disk. If the
|
session tokens (cumulative in/out) | decode speed t/s (thinking blocks) |
|
||||||
|
prompt-cache hit% (+ttl, cold marker) | disk. If the
|
||||||
rendered line would overflow the terminal, trailing (lowest-priority) segments are
|
rendered line would overflow the terminal, trailing (lowest-priority) segments are
|
||||||
dropped until it fits.
|
dropped until it fits.
|
||||||
(Dormant, re-addable in main(): ram, cpu%, temp -- retired 2026-07-26 because the
|
(Dormant, re-addable in main(): ram, cpu%, temp -- retired 2026-07-26 because the
|
||||||
status line only refreshes on message events, so system metrics sat visibly stale;
|
status line then only refreshed on message events, so system metrics sat visibly stale
|
||||||
|
(settings.json's 60 s refreshInterval, added for the PR count, is still too coarse for them);
|
||||||
tmux's status bar owns those now -- plus velocity, cache hit %, api ratio, version,
|
tmux's status bar owns those now -- plus velocity, cache hit %, api ratio, version,
|
||||||
style.)
|
style.)
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ Reads the status JSON from stdin (Claude Code statusLine command). Every segment
|
|||||||
is wrapped defensively so the status line can never crash the UI -- on any error
|
is wrapped defensively so the status line can never crash the UI -- on any error
|
||||||
a segment is simply omitted.
|
a segment is simply omitted.
|
||||||
"""
|
"""
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -159,6 +162,9 @@ def last_usage(data):
|
|||||||
# --- session token totals (transcript, incremental) ---------------------------
|
# --- session token totals (transcript, incremental) ---------------------------
|
||||||
TOKENS_STATE_DIR = os.path.expanduser("~/.claude/cache")
|
TOKENS_STATE_DIR = os.path.expanduser("~/.claude/cache")
|
||||||
TOKENS_RECENT_IDS = 20 # message ids remembered across runs for de-duplication
|
TOKENS_RECENT_IDS = 20 # message ids remembered across runs for de-duplication
|
||||||
|
TPS_SAMPLES = 8 # thinking blocks the t/s readout is averaged over
|
||||||
|
TPS_MIN_MS, TPS_MIN_TOKENS = 1000, 50 # shorter blocks are first-token latency, not decode speed
|
||||||
|
TOKENS_SCAN_BUDGET = 8 << 20 # bytes parsed per refresh (~0.1 s); see session_tokens
|
||||||
TOKENS_STATE_MAX_AGE = 7 * 86400 # prune per-session state files older than this
|
TOKENS_STATE_MAX_AGE = 7 * 86400 # prune per-session state files older than this
|
||||||
|
|
||||||
|
|
||||||
@@ -183,9 +189,10 @@ def _tokens_state_path(data, path):
|
|||||||
|
|
||||||
|
|
||||||
def _prune_tokens_state(keep):
|
def _prune_tokens_state(keep):
|
||||||
"""Drop stale per-session state files (one scandir, only when the dir has piled up)."""
|
"""Drop stale per-session token and per-branch PR state files (one scandir, only when
|
||||||
|
the dir has piled up)."""
|
||||||
try:
|
try:
|
||||||
entries = [e for e in os.scandir(TOKENS_STATE_DIR) if e.name.startswith("statusline.tokens.")]
|
entries = [e for e in os.scandir(TOKENS_STATE_DIR) if e.name.startswith(("statusline.tokens.", "statusline.pr."))]
|
||||||
if len(entries) < 50:
|
if len(entries) < 50:
|
||||||
return
|
return
|
||||||
cutoff = time.time() - TOKENS_STATE_MAX_AGE
|
cutoff = time.time() - TOKENS_STATE_MAX_AGE
|
||||||
@@ -197,12 +204,21 @@ def _prune_tokens_state(keep):
|
|||||||
|
|
||||||
|
|
||||||
def session_tokens(data):
|
def session_tokens(data):
|
||||||
"""Cumulative (input, output) tokens this session, summed over the transcript's
|
"""Cumulative session token totals plus decode-speed samples, from the transcript --
|
||||||
assistant `message.usage` blocks -- or None when there is no transcript.
|
`{"in", "out", "tps"}` or None when there is no transcript.
|
||||||
|
|
||||||
Input = input + cache_creation + cache_read (everything the API billed as input), so
|
in/out: summed over assistant `message.usage` blocks. Input = input + cache_creation +
|
||||||
on a 1M session it dwarfs output: every call re-reads the whole cached prefix.
|
cache_read (everything the API billed as input), so on a 1M session it dwarfs output:
|
||||||
Only the main transcript is counted; subagent usage lives in separate sidechain files.
|
every call re-reads the whole cached prefix. Only the main transcript is counted;
|
||||||
|
subagent usage lives in separate sidechain files.
|
||||||
|
|
||||||
|
tps: the last TPS_SAMPLES `[thinking_tokens, thinkingDurationMs]` pairs -- the only
|
||||||
|
clean per-call timing Claude Code records. The status payload's cost.total_api_duration_ms
|
||||||
|
is useless for a rate: subagent and advisor calls land in the same counter (it grew 28s
|
||||||
|
in a 12s wall interval with no main-conversation completion), and the payload's
|
||||||
|
output_tokens is sometimes a mid-stream partial. thinkingDurationMs is main-conversation
|
||||||
|
only; it includes first-token latency, so short blocks read low and long ones converge
|
||||||
|
on the real decode speed -- hence the duration-weighted mean and the minimum-size filter.
|
||||||
|
|
||||||
Incremental: the status line runs on every refresh and transcripts grow to tens of
|
Incremental: the status line runs on every refresh and transcripts grow to tens of
|
||||||
MB, so a per-session state file (~/.claude/cache/statusline.tokens.<session>.json)
|
MB, so a per-session state file (~/.claude/cache/statusline.tokens.<session>.json)
|
||||||
@@ -210,12 +226,18 @@ def session_tokens(data):
|
|||||||
de-duplicated by message id: the transcript writes one line per content block of a
|
de-duplicated by message id: the transcript writes one line per content block of a
|
||||||
turn, all carrying the same id and the same usage block. Only complete lines are
|
turn, all carrying the same id and the same usage block. Only complete lines are
|
||||||
consumed (the writer may be mid-line); a shrunken file (truncate/rotate) resets.
|
consumed (the writer may be mid-line); a shrunken file (truncate/rotate) resets.
|
||||||
|
|
||||||
|
A cold scan is spread over refreshes, TOKENS_SCAN_BUDGET bytes each: Claude Code kills
|
||||||
|
a status script when the next refresh fires, and a multi-second scan of a 100 MB
|
||||||
|
transcript that never gets to write its state would start from zero every time.
|
||||||
|
Totals climb for a few refreshes on a huge resumed transcript, then settle.
|
||||||
"""
|
"""
|
||||||
path = data.get("transcript_path")
|
path = data.get("transcript_path")
|
||||||
if not path or not os.path.exists(path):
|
if not path or not os.path.exists(path):
|
||||||
return None
|
return None
|
||||||
state_path = _tokens_state_path(data, path)
|
state_path = _tokens_state_path(data, path)
|
||||||
state = {"path": path, "offset": 0, "in": 0, "out": 0, "ids": []}
|
fresh = {"path": path, "offset": 0, "in": 0, "out": 0, "ids": [], "tps": [], "tps_ids": []}
|
||||||
|
state = dict(fresh)
|
||||||
try:
|
try:
|
||||||
with open(state_path) as fh:
|
with open(state_path) as fh:
|
||||||
s = json.load(fh)
|
s = json.load(fh)
|
||||||
@@ -225,27 +247,36 @@ def session_tokens(data):
|
|||||||
and all(isinstance(s.get(k), int) and s[k] >= 0 for k in ("offset", "in", "out"))
|
and all(isinstance(s.get(k), int) and s[k] >= 0 for k in ("offset", "in", "out"))
|
||||||
and isinstance(s.get("ids"), list)
|
and isinstance(s.get("ids"), list)
|
||||||
):
|
):
|
||||||
state = s
|
state = s # "tps"/"tps_ids" are optional: older state files predate them
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def result(st):
|
||||||
|
return {"in": st["in"], "out": st["out"], "tps": st.get("tps") or []}
|
||||||
|
|
||||||
size = os.path.getsize(path)
|
size = os.path.getsize(path)
|
||||||
if size < state["offset"]: # truncated or rotated under us: start over
|
if size < state["offset"]: # truncated or rotated under us: start over
|
||||||
state = {"path": path, "offset": 0, "in": 0, "out": 0, "ids": []}
|
state = dict(fresh)
|
||||||
if size == state["offset"]:
|
if size == state["offset"]:
|
||||||
return state["in"], state["out"]
|
return result(state)
|
||||||
|
|
||||||
with open(path, "rb") as fh:
|
with open(path, "rb") as fh:
|
||||||
fh.seek(state["offset"])
|
fh.seek(state["offset"])
|
||||||
chunk = fh.read()
|
chunk = fh.read(TOKENS_SCAN_BUDGET)
|
||||||
|
end = chunk.rfind(b"\n")
|
||||||
|
if end < 0 and len(chunk) == TOKENS_SCAN_BUDGET: # one line bigger than the budget: finish it
|
||||||
|
chunk += fh.readline()
|
||||||
end = chunk.rfind(b"\n")
|
end = chunk.rfind(b"\n")
|
||||||
if end < 0: # no complete new line yet
|
if end < 0: # no complete new line yet
|
||||||
return state["in"], state["out"]
|
return result(state)
|
||||||
text = chunk[: end + 1].decode("utf-8", "ignore")
|
text = chunk[: end + 1].decode("utf-8", "ignore")
|
||||||
|
|
||||||
tin, tout = state["in"], state["out"]
|
tin, tout = state["in"], state["out"]
|
||||||
ids = [i for i in state["ids"] if isinstance(i, str)]
|
ids = [i for i in state["ids"] if isinstance(i, str)]
|
||||||
seen = set(ids)
|
seen = set(ids)
|
||||||
|
tps = [s for s in (state.get("tps") or []) if isinstance(s, list) and len(s) == 2]
|
||||||
|
tps_ids = [i for i in (state.get("tps_ids") or []) if isinstance(i, str)]
|
||||||
|
tps_seen = set(tps_ids)
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
if '"usage"' not in line:
|
if '"usage"' not in line:
|
||||||
continue
|
continue
|
||||||
@@ -258,19 +289,38 @@ def session_tokens(data):
|
|||||||
if not isinstance(usage, dict):
|
if not isinstance(usage, dict):
|
||||||
continue
|
continue
|
||||||
mid = msg.get("id") or obj.get("requestId") or obj.get("uuid")
|
mid = msg.get("id") or obj.get("requestId") or obj.get("uuid")
|
||||||
if not isinstance(mid, str) or mid in seen:
|
if not isinstance(mid, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
def num(d, k):
|
||||||
|
v = d.get(k, 0) if isinstance(d, dict) else 0
|
||||||
|
return int(v) if isinstance(v, (int, float)) else 0
|
||||||
|
|
||||||
|
# Decode-speed sample: checked on every line (not just the first-wins one below) so
|
||||||
|
# it does not depend on the thinking block being the message's first line.
|
||||||
|
ms = num(obj, "thinkingDurationMs")
|
||||||
|
toks = num(usage.get("output_tokens_details"), "thinking_tokens")
|
||||||
|
if ms >= TPS_MIN_MS and toks >= TPS_MIN_TOKENS and mid not in tps_seen:
|
||||||
|
tps_seen.add(mid)
|
||||||
|
tps_ids.append(mid)
|
||||||
|
tps.append([toks, ms])
|
||||||
|
|
||||||
|
if mid in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(mid)
|
seen.add(mid)
|
||||||
ids.append(mid)
|
ids.append(mid)
|
||||||
|
tin += num(usage, "input_tokens") + num(usage, "cache_creation_input_tokens") + num(usage, "cache_read_input_tokens")
|
||||||
|
tout += num(usage, "output_tokens")
|
||||||
|
|
||||||
def num(k):
|
state = {
|
||||||
v = usage.get(k, 0)
|
"path": path,
|
||||||
return int(v) if isinstance(v, (int, float)) else 0
|
"offset": state["offset"] + end + 1,
|
||||||
|
"in": tin,
|
||||||
tin += num("input_tokens") + num("cache_creation_input_tokens") + num("cache_read_input_tokens")
|
"out": tout,
|
||||||
tout += num("output_tokens")
|
"ids": ids[-TOKENS_RECENT_IDS:],
|
||||||
|
"tps": tps[-TPS_SAMPLES:],
|
||||||
state = {"path": path, "offset": state["offset"] + end + 1, "in": tin, "out": tout, "ids": ids[-TOKENS_RECENT_IDS:]}
|
"tps_ids": tps_ids[-TOKENS_RECENT_IDS:],
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
os.makedirs(TOKENS_STATE_DIR, exist_ok=True)
|
os.makedirs(TOKENS_STATE_DIR, exist_ok=True)
|
||||||
tmp = f"{state_path}.{os.getpid()}.tmp"
|
tmp = f"{state_path}.{os.getpid()}.tmp"
|
||||||
@@ -280,7 +330,7 @@ def session_tokens(data):
|
|||||||
_prune_tokens_state(state_path)
|
_prune_tokens_state(state_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return tin, tout
|
return result(state)
|
||||||
|
|
||||||
|
|
||||||
# --- row 1: work ------------------------------------------------------------
|
# --- row 1: work ------------------------------------------------------------
|
||||||
@@ -395,6 +445,122 @@ def git_segment(cwd, top):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# --- PR review threads (gh, cached, fetched in the background) ------------------
|
||||||
|
PR_TTL = 90 # seconds a fetched count stays fresh
|
||||||
|
PR_ERR_TTL = 600 # back-off after a failed fetch (no GitHub remote, gh not authed, offline)
|
||||||
|
PR_CLAIM = 30 # a refresher spawned this recently is still running: don't start another
|
||||||
|
PR_CODEX = re.compile(r"codex", re.I) # chatgpt-codex-connector; same match as /pr-loop
|
||||||
|
PR_QUERY = """query($owner:String!,$repo:String!,$branch:String!){
|
||||||
|
repository(owner:$owner,name:$repo){
|
||||||
|
pullRequests(headRefName:$branch,states:OPEN,first:1,orderBy:{field:UPDATED_AT,direction:DESC}){
|
||||||
|
nodes{ number reviewThreads(first:100){ pageInfo{hasNextPage}
|
||||||
|
nodes{ isResolved comments(first:1){ nodes{ author{login} } } } } } } } }"""
|
||||||
|
|
||||||
|
|
||||||
|
def _pr_cache_path(top, branch):
|
||||||
|
key = hashlib.sha1(f"{top}\0{branch}".encode()).hexdigest()[:16]
|
||||||
|
return os.path.join(TOKENS_STATE_DIR, f"statusline.pr.{key}.json")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_json(path):
|
||||||
|
try:
|
||||||
|
with open(path) as fh:
|
||||||
|
d = json.load(fh)
|
||||||
|
return d if isinstance(d, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def pr_refresh(cwd, branch, path):
|
||||||
|
"""Background half of pr_segment (`statusline.py --pr-refresh CWD BRANCH CACHE`): fetch the
|
||||||
|
branch's open PR and write its unresolved review-thread counts to the cache. `{owner}` /
|
||||||
|
`{repo}` are gh placeholders resolved from the repo at `cwd`. A failed fetch keeps the last
|
||||||
|
counts and flags them stale; a branch without an open PR clears them."""
|
||||||
|
state = _read_json(path)
|
||||||
|
state["ts"] = time.time()
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["gh", "api", "graphql", "-F", "owner={owner}", "-F", "repo={repo}",
|
||||||
|
"-f", f"branch={branch}", "-f", f"query={PR_QUERY}"],
|
||||||
|
cwd=cwd, capture_output=True, text=True, timeout=20,
|
||||||
|
)
|
||||||
|
if r.returncode != 0:
|
||||||
|
raise RuntimeError(r.stderr)
|
||||||
|
nodes = json.loads(r.stdout)["data"]["repository"]["pullRequests"]["nodes"]
|
||||||
|
state = {"ts": state["ts"]}
|
||||||
|
if nodes:
|
||||||
|
threads = nodes[0]["reviewThreads"]
|
||||||
|
# a thread belongs to whoever opened it: its first comment's author
|
||||||
|
authors = [((t["comments"]["nodes"] or [{}])[0].get("author") or {}).get("login") or ""
|
||||||
|
for t in threads["nodes"] if not t["isResolved"]]
|
||||||
|
state.update(number=nodes[0]["number"], open=len(authors),
|
||||||
|
codex=sum(1 for a in authors if PR_CODEX.search(a)),
|
||||||
|
more=threads["pageInfo"]["hasNextPage"])
|
||||||
|
except Exception:
|
||||||
|
state["error"] = True
|
||||||
|
try:
|
||||||
|
os.makedirs(TOKENS_STATE_DIR, exist_ok=True)
|
||||||
|
tmp = f"{path}.{os.getpid()}.tmp"
|
||||||
|
with open(tmp, "w") as fh:
|
||||||
|
json.dump(state, fh)
|
||||||
|
os.replace(tmp, path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def pr_segment(cwd, top):
|
||||||
|
"""Unresolved review threads (any author, outdated ones included) on the current branch's
|
||||||
|
open PR: `💬 #N ✓` when none are left, else the count with Codex's share broken out.
|
||||||
|
|
||||||
|
A GitHub round trip takes ~0.7 s, too slow for a render, so this only reads a per-branch
|
||||||
|
cache (~/.claude/cache/statusline.pr.<hash>.json) and, once it is older than PR_TTL,
|
||||||
|
spawns a detached `--pr-refresh` child to update it -- the new count shows on the next
|
||||||
|
render. PR-conversation comments are not counted: they have no resolved state, so a
|
||||||
|
summary or an LGTM would keep the count from ever reaching zero.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not top or not shutil.which("gh"):
|
||||||
|
return ""
|
||||||
|
branch = _git(cwd, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip()
|
||||||
|
if not branch or branch == "HEAD":
|
||||||
|
return ""
|
||||||
|
path = _pr_cache_path(top, branch)
|
||||||
|
state = _read_json(path)
|
||||||
|
if time.time() - (state.get("ts") or 0) > (PR_ERR_TTL if state.get("error") else PR_TTL):
|
||||||
|
claim = path + ".lock"
|
||||||
|
try:
|
||||||
|
claimed = time.time() - os.path.getmtime(claim) < PR_CLAIM
|
||||||
|
except OSError:
|
||||||
|
claimed = False
|
||||||
|
if not claimed:
|
||||||
|
os.makedirs(TOKENS_STATE_DIR, exist_ok=True)
|
||||||
|
with open(claim, "w"):
|
||||||
|
pass
|
||||||
|
# own session + no inherited stdout: Claude Code reads ours to EOF, and
|
||||||
|
# cancels an in-flight render when the next one triggers
|
||||||
|
subprocess.Popen(
|
||||||
|
[sys.executable, os.path.abspath(__file__), "--pr-refresh", cwd, branch, path],
|
||||||
|
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
num = state.get("number")
|
||||||
|
if not num:
|
||||||
|
return ""
|
||||||
|
n, codex = state.get("open") or 0, state.get("codex") or 0
|
||||||
|
out = "💬 " + color(f"#{num} ", "gray")
|
||||||
|
if not n:
|
||||||
|
out += color("✓", "green")
|
||||||
|
else:
|
||||||
|
out += color(f"{n}{'+' if state.get('more') else ''}", "yellow", bold=True)
|
||||||
|
if codex:
|
||||||
|
out += color(" · ", "gray") + color(f"codex {codex}", "red")
|
||||||
|
if state.get("error"):
|
||||||
|
out += color(" ?", "gray") # last fetch failed: these counts are stale
|
||||||
|
return out
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def model_segment(data):
|
def model_segment(data):
|
||||||
name = (data.get("model") or {}).get("display_name")
|
name = (data.get("model") or {}).get("display_name")
|
||||||
if not name:
|
if not name:
|
||||||
@@ -503,13 +669,12 @@ def velocity_segment(data):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def tokens_segment(data):
|
def tokens_segment(totals):
|
||||||
"""Cumulative session tokens: ↑ input (incl. cache reads/writes) and ↓ output."""
|
"""Cumulative session tokens: ↑ input (incl. cache reads/writes) and ↓ output."""
|
||||||
try:
|
try:
|
||||||
totals = session_tokens(data)
|
|
||||||
if not totals:
|
if not totals:
|
||||||
return ""
|
return ""
|
||||||
tin, tout = totals
|
tin, tout = totals["in"], totals["out"]
|
||||||
if not (tin or tout):
|
if not (tin or tout):
|
||||||
return ""
|
return ""
|
||||||
return "🔢 " + color(f"↑{fmt_tokens(tin)}", "cyan") + " " + color(f"↓{fmt_tokens(tout)}", "green")
|
return "🔢 " + color(f"↑{fmt_tokens(tin)}", "cyan") + " " + color(f"↓{fmt_tokens(tout)}", "green")
|
||||||
@@ -517,6 +682,22 @@ def tokens_segment(data):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def tps_segment(totals):
|
||||||
|
"""Decode speed in tokens/s: duration-weighted mean over the last few thinking blocks
|
||||||
|
(see session_tokens). Omitted until a thinking block ≥1s has completed -- so never
|
||||||
|
shown with thinking disabled."""
|
||||||
|
try:
|
||||||
|
samples = (totals or {}).get("tps") or []
|
||||||
|
toks = sum(s[0] for s in samples)
|
||||||
|
ms = sum(s[1] for s in samples)
|
||||||
|
if toks <= 0 or ms <= 0:
|
||||||
|
return ""
|
||||||
|
rate = toks / ms * 1000
|
||||||
|
return "⚡ " + color(f"{rate:.0f} t/s", bucket(rate, 20, 40, invert=True))
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def cache_segment(usage, data=None):
|
def cache_segment(usage, data=None):
|
||||||
"""Prompt-cache hit % (higher is better) + the cache TTL, `·cold` when the cache has
|
"""Prompt-cache hit % (higher is better) + the cache TTL, `·cold` when the cache has
|
||||||
expired. Hit % from the 2.1.260+ payload `prompt_cache.hit_ratio` (a 0..1 fraction, null
|
expired. Hit % from the 2.1.260+ payload `prompt_cache.hit_ratio` (a 0..1 fraction, null
|
||||||
@@ -697,6 +878,9 @@ def style_segment(data):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if sys.argv[1:2] == ["--pr-refresh"] and len(sys.argv) == 5:
|
||||||
|
pr_refresh(*sys.argv[2:])
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
data = json.load(sys.stdin)
|
data = json.load(sys.stdin)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -708,6 +892,7 @@ def main():
|
|||||||
or os.getcwd()
|
or os.getcwd()
|
||||||
)
|
)
|
||||||
usage = payload_usage(data) or last_usage(data)
|
usage = payload_usage(data) or last_usage(data)
|
||||||
|
totals = session_tokens(data)
|
||||||
top = git_top(cwd)
|
top = git_top(cwd)
|
||||||
|
|
||||||
# One row: work (left) then system (right), joined. If it would overflow the
|
# One row: work (left) then system (right), joined. If it would overflow the
|
||||||
@@ -720,11 +905,13 @@ def main():
|
|||||||
vim_segment(data),
|
vim_segment(data),
|
||||||
dir_segment(cwd, top),
|
dir_segment(cwd, top),
|
||||||
git_segment(cwd, top),
|
git_segment(cwd, top),
|
||||||
|
pr_segment(cwd, top),
|
||||||
model_segment(data),
|
model_segment(data),
|
||||||
context_segment(data, usage),
|
context_segment(data, usage),
|
||||||
usage_segment(data),
|
usage_segment(data),
|
||||||
cost_segment(data),
|
cost_segment(data),
|
||||||
tokens_segment(data),
|
tokens_segment(totals),
|
||||||
|
tps_segment(totals),
|
||||||
cache_segment(usage, data),
|
cache_segment(usage, data),
|
||||||
) if s]
|
) if s]
|
||||||
system = [s for s in (
|
system = [s for s in (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
|
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
|
||||||
"logo": {
|
"logo": {
|
||||||
"source": "$(find \"${XDG_CONFIG_HOME:-$HOME/.config}/fastfetch/pngs/\" -name \"*.png\" | shuf -n 1)",
|
"source": "$(find \"${XDG_CONFIG_HOME:-$HOME/.config}/fastfetch/pngs/\" -name \"*.png\" -xtype f | shuf -n 1)",
|
||||||
"height": 18
|
"height": 18
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,27 @@
|
|||||||
|
# Mark the tmux window when a long shell command finishes, through the same
|
||||||
|
# ~/.config/tmux/scripts/agent-state.sh the coding agents use (tool tag "sh"):
|
||||||
|
# ✓ on exit 0, ! on failure — coloured when it happened while you were elsewhere, grey once
|
||||||
|
# visited, gone at the next command. Only commands longer than $agent_state_min_ms
|
||||||
|
# (default 30 s) count, and interactive programs are skipped: the agents mark themselves,
|
||||||
|
# and quitting a long nvim or lazygit session is not news. No-op outside tmux.
|
||||||
|
set -q agent_state_min_ms; or set -g agent_state_min_ms 30000
|
||||||
|
|
||||||
|
function __agent_state_preexec --on-event fish_preexec
|
||||||
|
set -q TMUX_PANE; or return
|
||||||
|
~/.config/tmux/scripts/agent-state.sh sh busy
|
||||||
|
end
|
||||||
|
|
||||||
|
function __agent_state_postexec --on-event fish_postexec
|
||||||
|
set -l st $status # first statement: anything before it would clobber the command's status
|
||||||
|
set -q TMUX_PANE; or return
|
||||||
|
test "$CMD_DURATION" -gt "$agent_state_min_ms"; or return
|
||||||
|
switch (string split -m1 ' ' -- $argv[1])[1]
|
||||||
|
case nvim vim vi less man lazygit ssh mosh tmux claude claudes pi opencode opencodes codex aichat htop btop top journalctl tail watch
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if test $st -eq 0
|
||||||
|
~/.config/tmux/scripts/agent-state.sh sh done
|
||||||
|
else
|
||||||
|
~/.config/tmux/scripts/agent-state.sh sh error
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Drop kitty's KITTY_PUBLIC_KEY from the shell environment.
|
||||||
|
#
|
||||||
|
# kitty generates a random 40-char base85 public key per instance and exports it
|
||||||
|
# to every child (kitty/child.py). It is only read on the password path of kitty
|
||||||
|
# remote control (kitty/remote_control.py: "Password usage requested but
|
||||||
|
# KITTY_PUBLIC_KEY environment variable is not available"), which needs
|
||||||
|
# allow_remote_control — not enabled here. `kitten ssh`, icat, clipboard and
|
||||||
|
# shell integration never touch it.
|
||||||
|
#
|
||||||
|
# Why bother: tools that inline the environment into generated code get corrupted
|
||||||
|
# when a value contains a String.replace replacement sequence. prettier's
|
||||||
|
# --experimental-cli inlines JSON.stringify(process.env) into its parallel worker
|
||||||
|
# and splices it with .replace(placeholder, bootloader); a `$` + backtick in the
|
||||||
|
# key expands to "everything before the match", injecting the worker's own source
|
||||||
|
# mid-string. Every file then fails with a bogus
|
||||||
|
# "SyntaxError: Unexpected identifier 'string'". Roughly a 1-in-185 chance per
|
||||||
|
# kitty launch, which is exactly as fun to debug as it sounds.
|
||||||
|
if set -q KITTY_PUBLIC_KEY
|
||||||
|
set -e KITTY_PUBLIC_KEY
|
||||||
|
end
|
||||||
@@ -106,7 +106,7 @@ if status is-interactive
|
|||||||
abbr scrcpys 'scrcpy -wS --power-off-on-close'
|
abbr scrcpys 'scrcpy -wS --power-off-on-close'
|
||||||
abbr tts "tt -notheme -bold -showwpm -json"
|
abbr tts "tt -notheme -bold -showwpm -json"
|
||||||
abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit"
|
abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit"
|
||||||
abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf"
|
abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/dots_binds.lua"
|
||||||
abbr vpnhome "sudo wg-quick up wg0"
|
abbr vpnhome "sudo wg-quick up wg0"
|
||||||
abbr vpnkralizec "sudo wg-quick up kralizec-wg0"
|
abbr vpnkralizec "sudo wg-quick up kralizec-wg0"
|
||||||
abbr vpnsumadija "sudo wg-quick up sumadija-wg0"
|
abbr vpnsumadija "sudo wg-quick up sumadija-wg0"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
function glow --wraps glow --description 'glow wrapped at the current terminal width; an explicit -w still wins'
|
||||||
|
# glow never sizes itself to the terminal: glow.yml's width (or its built-in 80) applies, and
|
||||||
|
# `width: 0` in the config means 80 too. Wide tables get squeezed into that, one word per line.
|
||||||
|
# $COLUMNS tracks window resizes; a later -w/--width in $argv overrides this one.
|
||||||
|
command glow --width $COLUMNS $argv
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
function md2pdf --description 'Markdown to PDF next to the source: cmark-gfm renders it, headless chromium prints it'
|
||||||
|
# Relative image paths don't resolve (the HTML is rendered from /tmp), and chromium
|
||||||
|
# leaves out backgrounds when printing, so code blocks lose their grey box.
|
||||||
|
set -l html (mktemp --suffix=.html)
|
||||||
|
begin
|
||||||
|
echo '<meta charset="utf-8"><style>body{font-family:sans-serif;max-width:46em;margin:auto;line-height:1.5}table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:.3em .6em}</style>'
|
||||||
|
cmark-gfm -e table -e strikethrough -e autolink -e tasklist $argv[1]
|
||||||
|
end >$html
|
||||||
|
chromium --headless --no-pdf-header-footer --print-to-pdf=(path change-extension pdf $argv[1]) $html 2>/dev/null
|
||||||
|
rm $html
|
||||||
|
end
|
||||||
@@ -35,4 +35,4 @@ traceroute
|
|||||||
fuck
|
fuck
|
||||||
atool
|
atool
|
||||||
aria2c
|
aria2c
|
||||||
|
cmark-gfm
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
{
|
{
|
||||||
"LuaSnip": { "branch": "master", "commit": "0abc8f390b278c3b4aabc4c004ac8a088b65cf24" },
|
"LuaSnip": { "branch": "master", "commit": "0abc8f390b278c3b4aabc4c004ac8a088b65cf24" },
|
||||||
"NvChad": { "branch": "v2.5", "commit": "add44b952d631981614bbb8cfc6f7002f296dfe6" },
|
"NvChad": { "branch": "v2.5", "commit": "87d6a41ec07ab995de912a892749b6b4805ad2b3" },
|
||||||
"base46": { "branch": "v3.0", "commit": "267954c8663607823f03a3259bb8deb15688212f" },
|
"base46": { "branch": "v3.0", "commit": "d870ec89a7b2f28c4accce515f97b7bcc5b9d200" },
|
||||||
"cmp-async-path": { "branch": "main", "commit": "98185a91d49ff5dd249aebf2f7456e18063fa2a0" },
|
"cmp-async-path": { "branch": "main", "commit": "98185a91d49ff5dd249aebf2f7456e18063fa2a0" },
|
||||||
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
|
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
|
||||||
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
|
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
|
||||||
"cmp-nvim-lua": { "branch": "main", "commit": "e3a22cb071eb9d6508a156306b102c45cd2d573d" },
|
"cmp-nvim-lua": { "branch": "main", "commit": "e3a22cb071eb9d6508a156306b102c45cd2d573d" },
|
||||||
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
|
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
|
||||||
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
|
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
|
||||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
"friendly-snippets": { "branch": "main", "commit": "b2ea5c41a150ba72fb656eedb4ed0a86d138d945" },
|
||||||
"gitsigns.nvim": { "branch": "main", "commit": "d29ecf6626cf6ffb109fb04a8ecd92c46f4a1ce9" },
|
"gitsigns.nvim": { "branch": "main", "commit": "4bcd99167c265f135886b282baad77afe06206b2" },
|
||||||
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
|
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
|
||||||
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
|
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
|
||||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
||||||
"menu": { "branch": "main", "commit": "7a0a4a2896b715c066cfbe320bdc048091874cc6" },
|
"menu": { "branch": "main", "commit": "7a0a4a2896b715c066cfbe320bdc048091874cc6" },
|
||||||
"minty": { "branch": "main", "commit": "aafc9e8e0afe6bf57580858a2849578d8d8db9e0" },
|
"minty": { "branch": "main", "commit": "aafc9e8e0afe6bf57580858a2849578d8d8db9e0" },
|
||||||
"nvim-autopairs": { "branch": "master", "commit": "7b9923abad60b903ece7c52940e1321d39eccc79" },
|
"nvim-autopairs": { "branch": "master", "commit": "3172f40c16563db871a58f20d205c710ad782311" },
|
||||||
"nvim-cmp": { "branch": "main", "commit": "2ffe79f1f021def8dd1fcd81deb16f1bb0d989f3" },
|
"nvim-cmp": { "branch": "main", "commit": "e04302f1fefcb5653848346eb3723b39b82c549b" },
|
||||||
"nvim-lspconfig": { "branch": "master", "commit": "4267d26e60323a45e18f8e7a63a964cc8f53ca4e" },
|
"nvim-lspconfig": { "branch": "master", "commit": "cc404d425c7752f56b8eaa269107be44d888ea51" },
|
||||||
"nvim-tree.lua": { "branch": "master", "commit": "b2aadda94b107480c48e548d6db51c6840b7b33c" },
|
"nvim-tree.lua": { "branch": "master", "commit": "69eb0c34ed3d836d5a0d92d913b77dd73b054f2f" },
|
||||||
"nvim-treesitter": { "branch": "main", "commit": "c9f9ed6c1892f629ea399f4ee7905f2686fa13f2" },
|
"nvim-treesitter": { "branch": "main", "commit": "610734c5d3e59761d5f79318d120633374537fa5" },
|
||||||
"nvim-web-devicons": { "branch": "master", "commit": "2ae6958df7ced50baac5035cec0c15799eedfbf7" },
|
"nvim-web-devicons": { "branch": "master", "commit": "ec309a0eaff6ea967e58c275996155ab5c16938d" },
|
||||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
||||||
"render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" },
|
"render-markdown.nvim": { "branch": "main", "commit": "8e8795368391036643b622875a6da6a51bcd33f4" },
|
||||||
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
||||||
"telescope.nvim": { "branch": "master", "commit": "427b576c16792edad01a92b89721d923c19ad60f" },
|
"telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
|
||||||
"ui": { "branch": "v3.0", "commit": "fe781d1c12860d6a25d45e588fe4fdd27eb34a1a" },
|
"ui": { "branch": "v3.0", "commit": "8cbf0dc1883b20a1df114149c187d250f0af0853" },
|
||||||
"volt": { "branch": "main", "commit": "620de1321f275ec9d80028c68d1b88b409c0c8b1" },
|
"volt": { "branch": "main", "commit": "4351c5cd790a0d39491c4d5c75dd62d7db3d3414" },
|
||||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ service caches config).
|
|||||||
| `agents/*.md` | Four subagents with real prompts and scoped permissions: `code-reviewer`, `test-runner` (hidden), `doc-writer` (edits docs only), `security-auditor` (hidden). |
|
| `agents/*.md` | Four subagents with real prompts and scoped permissions: `code-reviewer`, `test-runner` (hidden), `doc-writer` (edits docs only), `security-auditor` (hidden). |
|
||||||
| `commands/*.md` | `/commit` (Conventional Commits message, never commits) and `/review` (read-only diff review), both on the `plan` agent. |
|
| `commands/*.md` | `/commit` (Conventional Commits message, never commits) and `/review` (read-only diff review), both on the `plan` agent. |
|
||||||
| `plugins/verbose/` | 2.x TUI plugin: `<leader>v`, `/verbose`, or the palette entry toggles the *verbose view* — thinking blocks expanded and tool calls ungrouped — reading the live state from the built-in toggles so a mixed state converges. `server.ts` is a no-op half required by the loader. |
|
| `plugins/verbose/` | 2.x TUI plugin: `<leader>v`, `/verbose`, or the palette entry toggles the *verbose view* — thinking blocks expanded and tool calls ungrouped — reading the live state from the built-in toggles so a mixed state converges. `server.ts` is a no-op half required by the loader. |
|
||||||
|
| `plugins/agent-state.ts` | Server-side plugin: mirrors the session state (`message.updated` user → busy, `permission.asked` → input, `session.idle` → done, `session.error` → error) into the tmux pane opencode runs in via `~/.config/tmux/scripts/agent-state.sh`, for the status-bar agent badges. Skips child sessions; only right when the TUI in the pane spawned the server (not `serve`/`attach`); under `opencodes` (bwrap) the tmux socket dir must be bound in. |
|
||||||
| `themes/catppuccin-mocha.json` | Catppuccin Mocha (dark) / Latte (light) theme following the pi theme's semantic mapping. Built-in `catppuccin` exists too; this one matches pi. |
|
| `themes/catppuccin-mocha.json` | Catppuccin Mocha (dark) / Latte (light) theme following the pi theme's semantic mapping. Built-in `catppuccin` exists too; this one matches pi. |
|
||||||
| `tui.json` | TUI settings in the v1 schema (2.x compat): theme, leader timeout, stacked diffs, block cursor, attention notifications, a few extra keybinds. |
|
| `tui.json` | TUI settings in the v1 schema (2.x compat): theme, leader timeout, stacked diffs, block cursor, attention notifications, a few extra keybinds. |
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s, remote)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s, remote)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
||||||
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
||||||
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s, remote)", "limit": { "context": 24576, "output": 8192 } },
|
||||||
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s, remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s, remote)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
|
||||||
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s, remote)", "limit": { "context": 65536, "output": 8192 } },
|
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s, remote)", "limit": { "context": 65536, "output": 8192 } },
|
||||||
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s, remote)", "limit": { "context": 65536, "output": 8192 } }
|
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s, remote)", "limit": { "context": 65536, "output": 8192 } }
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (~57 t/s)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
|
||||||
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
"GLM-4.7-Flash-UD-Q4_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash · 24k — quality coder (~21 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
||||||
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
"GLM-4.7-Flash-UD-Q3_K_XL": { "reasoning": true, "interleaved": "reasoning_content", "name": "GLM-4.7 Flash Q3 · 24k — fast coder (~28 t/s)", "limit": { "context": 24576, "output": 8192 } },
|
||||||
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
|
"Ornith-1.5-35B-A3B-IQ3_XXS": { "reasoning": true, "name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
|
||||||
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s)", "limit": { "context": 65536, "output": 8192 } },
|
"gpt-oss-20b": { "reasoning": true, "name": "gpt-oss 20B · 64k — fast reasoning + tools (~38 t/s)", "limit": { "context": 65536, "output": 8192 } },
|
||||||
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s)", "limit": { "context": 65536, "output": 8192 } }
|
"gpt-oss-20b-low": { "reasoning": true, "name": "gpt-oss 20B low · 64k — snappy answers (~37 t/s)", "limit": { "context": 65536, "output": 8192 } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// opencode agent-state plugin: mirrors the session state into the tmux pane opencode runs in via
|
||||||
|
// ~/.config/tmux/scripts/agent-state.sh (oc busy|done|input|error). No-op outside tmux.
|
||||||
|
//
|
||||||
|
// Runs in the SERVER process, so TMUX_PANE is only right when the TUI in the pane spawned the
|
||||||
|
// server (plain `opencode`), not for `opencode serve` / `opencode attach`. Under the bwrap
|
||||||
|
// `opencodes` wrapper the tmux socket dir must be bound in, or the script gives up quietly.
|
||||||
|
// Child (subagent) sessions are skipped so a subagent finishing does not flag "done".
|
||||||
|
import type { Plugin } from "@opencode-ai/plugin"
|
||||||
|
|
||||||
|
export const AgentState: Plugin = async ({ client, $ }) => {
|
||||||
|
const script = `${process.env.HOME}/.config/tmux/scripts/agent-state.sh`
|
||||||
|
const set = async (state: string) => {
|
||||||
|
if (!process.env.TMUX_PANE) return
|
||||||
|
try {
|
||||||
|
await $`${script} oc ${state}`.quiet().nothrow()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
const childCache = new Map<string, boolean>()
|
||||||
|
const isChild = async (sessionID?: string) => {
|
||||||
|
if (!sessionID) return false
|
||||||
|
if (!childCache.has(sessionID)) {
|
||||||
|
try {
|
||||||
|
const r = await client.session.get({ path: { id: sessionID } })
|
||||||
|
childCache.set(sessionID, Boolean(r.data?.parentID))
|
||||||
|
} catch {
|
||||||
|
childCache.set(sessionID, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return childCache.get(sessionID) ?? false
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
event: async ({ event }) => {
|
||||||
|
const p = (event as { properties?: Record<string, any> }).properties ?? {}
|
||||||
|
switch (event.type) {
|
||||||
|
case "message.updated": // a new user message = a prompt went in
|
||||||
|
if (p.info?.role === "user" && !(await isChild(p.info?.sessionID))) await set("busy")
|
||||||
|
break
|
||||||
|
case "permission.asked":
|
||||||
|
if (!(await isChild(p.sessionID))) await set("input")
|
||||||
|
break
|
||||||
|
case "permission.replied":
|
||||||
|
if (!(await isChild(p.sessionID))) await set("busy")
|
||||||
|
break
|
||||||
|
case "session.idle":
|
||||||
|
if (!(await isChild(p.sessionID))) await set("done")
|
||||||
|
break
|
||||||
|
case "session.error":
|
||||||
|
if (!(await isChild(p.sessionID))) await set("error")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# agent-next.sh: switch to the next window (after the current one, wrapping) that holds an
|
||||||
|
# agent mark you have not looked at yet; with none left, fall back to any grey mark; with
|
||||||
|
# nothing marked, say so. Bound to prefix+Tab and to the » tap target in the status bar.
|
||||||
|
cur=$(tmux display -p '#{window_index}')
|
||||||
|
pick() { # $1 empty = unseen marks only, "any" = grey ones too
|
||||||
|
tmux list-panes -a -F '#{window_index} #{@agent} #{@agent_seen}' |
|
||||||
|
awk -v c="$cur" -v any="$1" '
|
||||||
|
$2 != "" && $2 != "busy" && (any != "" || $3 == "") { if ($1 > c && !n) n = $1; if (!f) f = $1 }
|
||||||
|
END { if (n) print n; else if (f) print f }'
|
||||||
|
}
|
||||||
|
w=$(pick); [ -n "$w" ] || w=$(pick any)
|
||||||
|
if [ -n "$w" ]; then tmux select-window -t ":$w"; else tmux display-message "no agent waiting"; fi
|
||||||
Executable
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# agent-state.sh <cc|oc|pi|cx> <busy|done|input|error|off>
|
||||||
|
#
|
||||||
|
# Per-pane "what is the agent in this pane doing" for tmux, shared by every agent's hooks:
|
||||||
|
# Claude Code (claude/settings.json), codex (~/.codex/hooks.json), pi (agent-state.ts
|
||||||
|
# extension) and opencode (plugins/agent-state.ts). No-op outside tmux.
|
||||||
|
#
|
||||||
|
# Which pane: $TMUX_PANE when the agent runs in the pane itself (pi, opencode, codex). Claude
|
||||||
|
# Code sessions run in a worker the claude daemon pre-spawned ("claude bg-spare"), whose
|
||||||
|
# environment has no tmux variables, so for them the worker is found by walking up the parent
|
||||||
|
# chain to the pid registered in ~/.claude/sessions/<pid>.json, and the pane is the one whose
|
||||||
|
# title Claude set to that session's name ("✳ <name>").
|
||||||
|
#
|
||||||
|
# Pane options read by tmux.conf's @agent_badge: @agent (the state, drawn as ✓ ? ! after the
|
||||||
|
# window name; busy draws nothing), @agent_seen (set by tmux's pane-focus-in hook: the glyph
|
||||||
|
# loses its colour once you visit the pane and stays, grey, until the next prompt turns the
|
||||||
|
# state busy) and @agent_tool (only the termux notification title uses it). `off` unsets all.
|
||||||
|
#
|
||||||
|
# Phone-level notice only for a NEW mark on a pane that is not in view: termux-notification
|
||||||
|
# when the agent runs natively in termux, otherwise a BEL into the pane (tmux forwards the bell
|
||||||
|
# to the outer terminal; termux's default bell-character=vibrate buzzes, and the window gets
|
||||||
|
# tmux's bell flag). A state change on an already-marked pane (e.g. Claude's idle_prompt ~60s
|
||||||
|
# after Stop turning done into input) only swaps the glyph: no second buzz, seen stays as is.
|
||||||
|
command -v tmux >/dev/null 2>&1 || exit 0
|
||||||
|
tool=$1 state=$2 pane=$TMUX_PANE
|
||||||
|
if [ -z "$pane" ]; then
|
||||||
|
p=$$ worker=""
|
||||||
|
for _ in 1 2 3 4 5 6 7 8; do
|
||||||
|
p=$(ps -o ppid= -p "$p" 2>/dev/null | tr -d ' ')
|
||||||
|
[ -n "$p" ] && [ "$p" != 1 ] || break
|
||||||
|
[ -f "$HOME/.claude/sessions/$p.json" ] && { worker=$p; break; }
|
||||||
|
done
|
||||||
|
[ -n "$worker" ] || exit 0
|
||||||
|
name=$(python3 -I -c 'import json,sys; print(json.load(open(sys.argv[1])).get("name",""))' \
|
||||||
|
"$HOME/.claude/sessions/$worker.json" 2>/dev/null)
|
||||||
|
[ -n "$name" ] || exit 0
|
||||||
|
pane=$(tmux list-panes -a -F '#{pane_id} #{pane_title}' 2>/dev/null |
|
||||||
|
awk -F'\t' -v n="$name" '$2==n || substr($2,length($2)-length(n))==" " n {print $1; exit}')
|
||||||
|
[ -n "$pane" ] || exit 0
|
||||||
|
fi
|
||||||
|
if [ "$state" = off ]; then
|
||||||
|
for o in @agent @agent_seen @agent_tool; do tmux set -p -t "$pane" -u "$o" 2>/dev/null; done
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
tmux set -p -t "$pane" @agent_tool "$tool" 2>/dev/null || exit 0 # no server reachable (sandbox): give up quietly
|
||||||
|
case $state in
|
||||||
|
busy)
|
||||||
|
tmux set -p -t "$pane" @agent busy
|
||||||
|
tmux set -p -t "$pane" -u @agent_seen 2>/dev/null
|
||||||
|
command -v termux-notification-remove >/dev/null 2>&1 && termux-notification-remove "agent-$pane"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
already=$(tmux show -pv -t "$pane" @agent 2>/dev/null)
|
||||||
|
tmux set -p -t "$pane" @agent "$state"
|
||||||
|
if [ "$(tmux display -p -t "$pane" '#{pane_active}#{window_active}')" = 11 ]; then
|
||||||
|
tmux set -p -t "$pane" @agent_seen 1 # in view: you saw it happen, so it starts out seen
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
[ -n "$already" ] && [ "$already" != busy ] && exit 0 # glyph swapped on an existing mark
|
||||||
|
tmux set -p -t "$pane" -u @agent_seen 2>/dev/null
|
||||||
|
if command -v termux-notification >/dev/null 2>&1; then
|
||||||
|
termux-notification --id "agent-$pane" --title "$tool: $state" --content "${PWD##*/}" \
|
||||||
|
--vibrate 200 --action "tmux select-pane -t $pane"
|
||||||
|
else
|
||||||
|
printf '\a' > "$(tmux display -p -t "$pane" '#{pane_tty}')" 2>/dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# CPU utilisation (%) averaged over the interval since the PREVIOUS status refresh:
|
|
||||||
# /proc/stat totals persist in a state file between runs (same pattern as net.sh),
|
|
||||||
# so the value covers the whole status-interval. (The old version slept 0.3s and
|
|
||||||
# sampled just that window — a 6% duty cycle that aliased bursty loads.) No sleep,
|
|
||||||
# ~5ms. First run (no state yet) shows the since-boot average. Glyph = Waybar's
|
|
||||||
# cpu module icon (U+F035B).
|
|
||||||
printf '\U000f035b '
|
|
||||||
rt="${XDG_RUNTIME_DIR:-/tmp}"
|
|
||||||
st="$rt/tmux_cpu_stat" # previous totals: tot idle
|
|
||||||
|
|
||||||
read -r _ u n s i w rest </proc/stat
|
|
||||||
idle=$((i + w))
|
|
||||||
tot=$((u + n + s + i + w))
|
|
||||||
for v in $rest; do tot=$((tot + v)); done
|
|
||||||
|
|
||||||
p_tot=0
|
|
||||||
p_idle=0
|
|
||||||
[ -r "$st" ] && read -r p_tot p_idle <"$st"
|
|
||||||
[ "$p_tot" -gt "$tot" ] && {
|
|
||||||
p_tot=0
|
|
||||||
p_idle=0
|
|
||||||
} # stale state from a previous boot
|
|
||||||
printf '%s %s\n' "$tot" "$idle" >"$st"
|
|
||||||
|
|
||||||
dt=$((tot - p_tot))
|
|
||||||
di=$((idle - p_idle))
|
|
||||||
if [ "$dt" -le 0 ]; then printf -- '--%%'; else printf '%d%%' $(((100 * (dt - di) + dt / 2) / dt)); fi
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# RAM utilisation (%) — used = MemTotal - MemAvailable, from /proc/meminfo.
|
|
||||||
# Called from tmux status-right via #(); cached per status-interval.
|
|
||||||
printf '\U000f0f86 ' # Waybar's memory glyph (U+F0F86), then the percentage
|
|
||||||
awk '/^MemTotal:/{t=$2} /^MemAvailable:/{a=$2} END{if(t>0)printf "%d%%",(t-a)*100/t; else printf "--%%"}' /proc/meminfo
|
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# metrics.sh full|compact — the status-right system segment in ONE process per refresh.
|
||||||
|
# (tmux used to spawn net dl, net ul, cpu, mem and temp as five separate #() jobs per tick.)
|
||||||
|
# Prints #[fg=…] style markers, which tmux honours inside #() output.
|
||||||
|
# full : net dl · net ul · cpu · mem · temp (temp vanishes when no sensor exists)
|
||||||
|
# compact : cpu · mem
|
||||||
|
# CPU and net are averaged over the interval since the PREVIOUS refresh: the /proc totals
|
||||||
|
# persist in $XDG_RUNTIME_DIR state files, so no sleeps and the value covers the whole
|
||||||
|
# status-interval. First run (no state yet) shows the since-boot average. Glyphs are Waybar's:
|
||||||
|
# cpu U+F035B, memory U+F0F86, thermometer U+F050F, download U+F019, upload U+F093.
|
||||||
|
mode=${1:-full}
|
||||||
|
rt="${XDG_RUNTIME_DIR:-/tmp}"
|
||||||
|
|
||||||
|
cpu() { # utilisation % since the previous refresh
|
||||||
|
local st="$rt/tmux_cpu_stat" u n s i w rest idle tot v p_tot=0 p_idle=0 dt di
|
||||||
|
read -r _ u n s i w rest </proc/stat
|
||||||
|
idle=$((i + w))
|
||||||
|
tot=$((u + n + s + i + w))
|
||||||
|
for v in $rest; do tot=$((tot + v)); done
|
||||||
|
[ -r "$st" ] && read -r p_tot p_idle <"$st"
|
||||||
|
[ "$p_tot" -gt "$tot" ] && { p_tot=0; p_idle=0; } # stale state from a previous boot
|
||||||
|
printf '%s %s\n' "$tot" "$idle" >"$st"
|
||||||
|
dt=$((tot - p_tot))
|
||||||
|
di=$((idle - p_idle))
|
||||||
|
printf '\U000f035b '
|
||||||
|
if [ "$dt" -le 0 ]; then printf -- '--%%'; else printf '%d%%' $(((100 * (dt - di) + dt / 2) / dt)); fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mem() { # used = MemTotal - MemAvailable
|
||||||
|
printf '\U000f0f86 '
|
||||||
|
awk '/^MemTotal:/{t=$2} /^MemAvailable:/{a=$2} END{if(t>0)printf "%d%%",(t-a)*100/t; else printf "--%%"}' /proc/meminfo
|
||||||
|
}
|
||||||
|
|
||||||
|
temp() { # CPU °C: a CPU-ish thermal zone, else a CPU hwmon chip, else the hottest zone; nothing if none
|
||||||
|
local cpu_zone="" any_zone="" hw="" d t ty f
|
||||||
|
for d in /sys/class/thermal/thermal_zone*/; do
|
||||||
|
[ -r "${d}temp" ] && [ -r "${d}type" ] || continue
|
||||||
|
t=$(<"${d}temp")
|
||||||
|
case $t in '' | *[!0-9]*) continue ;; esac
|
||||||
|
ty=$(<"${d}type"); ty=${ty,,}
|
||||||
|
case $ty in *x86_pkg_temp* | *coretemp* | *k10temp* | *tctl* | *cpu*)
|
||||||
|
if [ -z "$cpu_zone" ] || [ "$t" -gt "$cpu_zone" ]; then cpu_zone=$t; fi ;;
|
||||||
|
esac
|
||||||
|
if [ -z "$any_zone" ] || [ "$t" -gt "$any_zone" ]; then any_zone=$t; fi
|
||||||
|
done
|
||||||
|
if [ -z "$cpu_zone" ]; then
|
||||||
|
for d in /sys/class/hwmon/hwmon*/; do
|
||||||
|
[ -r "${d}name" ] || continue
|
||||||
|
case $(<"${d}name") in coretemp | k10temp | zenpower | cpu_thermal)
|
||||||
|
for f in "${d}"temp*_input; do
|
||||||
|
[ -r "$f" ] || continue
|
||||||
|
t=$(<"$f")
|
||||||
|
case $t in '' | *[!0-9]*) continue ;; esac
|
||||||
|
if [ -z "$hw" ] || [ "$t" -gt "$hw" ]; then hw=$t; fi
|
||||||
|
done ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
t=${cpu_zone:-${hw:-$any_zone}}
|
||||||
|
[ -n "$t" ] || return 0
|
||||||
|
printf '\U000f050f %d° ' $(((t + 500) / 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
human() { awk -v b="$1" 'BEGIN{ split("B K M G T", u, " "); i=1;
|
||||||
|
while (b >= 1024 && i < 5) { b /= 1024; i++ }
|
||||||
|
if (i == 1) printf "%d%s", b, u[i]; else printf "%.1f%s", b, u[i] }'; }
|
||||||
|
|
||||||
|
net() { # sets dl/ul: bytes per second over all non-lo interfaces since the previous refresh
|
||||||
|
local ctr="$rt/tmux_net_ctr" now rx tx prx ptx pnow drx=0 dtx=0 dt=1
|
||||||
|
now=$(date +%s.%N)
|
||||||
|
read -r rx tx < <(awk 'NR>2{gsub(/:/," "); if($1!="lo"){r+=$2; t+=$10}} END{print r+0, t+0}' /proc/net/dev)
|
||||||
|
if [ -r "$ctr" ]; then
|
||||||
|
read -r prx ptx pnow <"$ctr"
|
||||||
|
dt=$(awk -v a="$now" -v b="$pnow" 'BEGIN{d=a-b; print (d>0.05)?d:1}')
|
||||||
|
drx=$((rx - prx)); dtx=$((tx - ptx))
|
||||||
|
[ "$drx" -lt 0 ] && drx=0
|
||||||
|
[ "$dtx" -lt 0 ] && dtx=0
|
||||||
|
fi
|
||||||
|
printf '%s %s %s\n' "$rx" "$tx" "$now" >"$ctr"
|
||||||
|
dl=$(human "$(awk -v x="$drx" -v d="$dt" 'BEGIN{printf "%d", x/d}')")
|
||||||
|
ul=$(human "$(awk -v x="$dtx" -v d="$dt" 'BEGIN{printf "%d", x/d}')")
|
||||||
|
}
|
||||||
|
|
||||||
|
case $mode in
|
||||||
|
compact)
|
||||||
|
printf '#[fg=#89b4fa]%s #[fg=#94e2d5]%s' "$(cpu)" "$(mem)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
net
|
||||||
|
printf '#[fg=#a6e3a1]\U0000f019 %s #[fg=#f9e2af]\U0000f093 %s #[fg=#89b4fa]%s #[fg=#94e2d5]%s #[fg=#fab387]%s' \
|
||||||
|
"$dl" "$ul" "$(cpu)" "$(mem)" "$(temp)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Network throughput from /proc/net/dev (sum of all non-lo interfaces), averaged
|
|
||||||
# over the interval between status refreshes — no blocking sleep.
|
|
||||||
#
|
|
||||||
# Usage: net.sh dl -> " 1.2M" (download icon U+F019 + rate)
|
|
||||||
# net.sh ul -> " 45K" (upload icon U+F093 + rate)
|
|
||||||
#
|
|
||||||
# tmux calls both from status-right. A short (<3s) cache lets the two calls of a
|
|
||||||
# single refresh share one computation, so the up/down figures are consistent and
|
|
||||||
# only one of them re-samples the counters per refresh.
|
|
||||||
which=${1:-dl}
|
|
||||||
rt="${XDG_RUNTIME_DIR:-/tmp}"
|
|
||||||
ctr="$rt/tmux_net_ctr" # previous counters: rx tx epoch.ns
|
|
||||||
cache="$rt/tmux_net_cache" # computed rates: dl_rate ul_rate epoch_s
|
|
||||||
|
|
||||||
emit() { # emit <icon-printf-escape> <rate>
|
|
||||||
printf "$1 %s" "$2"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reuse a fresh cache so dl and ul agree and we only sample once per refresh.
|
|
||||||
fresh_emit() { # print from the cache and exit — if the cache is fresh enough
|
|
||||||
[ -r "$cache" ] || return 1
|
|
||||||
read -r cdl cul cts <"$cache"
|
|
||||||
{ [ -n "$cts" ] && [ "$(($(date +%s) - cts))" -lt 3 ]; } || return 1
|
|
||||||
if [ "$which" = ul ]; then emit '\U0000f093' "$cul"; else emit '\U0000f019' "$cdl"; fi
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fresh_emit
|
|
||||||
|
|
||||||
# tmux launches the dl and ul jobs CONCURRENTLY each refresh, so without a lock
|
|
||||||
# both miss the stale cache and compute independently — worst case the loser reads
|
|
||||||
# the counters milliseconds after the winner rewrote them and shows a ~0 rate.
|
|
||||||
# Serialize: the loser waits ~30ms on the lock, then hits the winner's fresh cache.
|
|
||||||
exec 9>"$rt/tmux_net_lock"
|
|
||||||
flock 9
|
|
||||||
fresh_emit
|
|
||||||
|
|
||||||
now=$(date +%s.%N)
|
|
||||||
read -r rx tx < <(awk 'NR>2{gsub(/:/," "); if($1!="lo"){r+=$2; t+=$10}} END{print r+0, t+0}' /proc/net/dev)
|
|
||||||
|
|
||||||
drx=0
|
|
||||||
dtx=0
|
|
||||||
dt=1
|
|
||||||
if [ -r "$ctr" ]; then
|
|
||||||
read -r prx ptx pnow <"$ctr"
|
|
||||||
dt=$(awk -v a="$now" -v b="$pnow" 'BEGIN{d=a-b; print (d>0.05)?d:1}')
|
|
||||||
drx=$((rx - prx))
|
|
||||||
dtx=$((tx - ptx))
|
|
||||||
[ "$drx" -lt 0 ] && drx=0
|
|
||||||
[ "$dtx" -lt 0 ] && dtx=0
|
|
||||||
fi
|
|
||||||
printf '%s %s %s\n' "$rx" "$tx" "$now" >"$ctr"
|
|
||||||
|
|
||||||
human() { awk -v b="$1" 'BEGIN{ split("B K M G T", u, " "); i=1;
|
|
||||||
while (b >= 1024 && i < 5) { b /= 1024; i++ }
|
|
||||||
if (i == 1) printf "%d%s", b, u[i]; else printf "%.1f%s", b, u[i] }'; }
|
|
||||||
|
|
||||||
dl=$(human "$(awk -v x="$drx" -v d="$dt" 'BEGIN{printf "%d", x/d}')")
|
|
||||||
ul=$(human "$(awk -v x="$dtx" -v d="$dt" 'BEGIN{printf "%d", x/d}')")
|
|
||||||
printf '%s %s %s\n' "$dl" "$ul" "$(date +%s)" >"$cache"
|
|
||||||
|
|
||||||
if [ "$which" = ul ]; then emit '\U0000f093' "$ul"; else emit '\U0000f019' "$dl"; fi
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# CPU temperature (°C) for the tmux status bar (thermometer glyph U+F050F).
|
|
||||||
# Sensor hunt, most→least specific: a CPU-ish thermal zone (x86_pkg_temp/…),
|
|
||||||
# else a CPU hwmon chip (coretemp/k10temp/…), else the hottest remaining zone
|
|
||||||
# (acpitz can be a bogus constant on desktops, hence last). Prints NOTHING when
|
|
||||||
# no sensor exists (VMs/containers) so the segment simply disappears — which is
|
|
||||||
# also why the trailing spacing lives in here, not in status-right.
|
|
||||||
cpu_zone=""
|
|
||||||
any_zone=""
|
|
||||||
hw=""
|
|
||||||
|
|
||||||
for d in /sys/class/thermal/thermal_zone*/; do
|
|
||||||
[ -r "${d}temp" ] && [ -r "${d}type" ] || continue
|
|
||||||
t=$(<"${d}temp")
|
|
||||||
case $t in '' | *[!0-9]*) continue ;; esac
|
|
||||||
ty=$(<"${d}type")
|
|
||||||
ty=${ty,,}
|
|
||||||
case $ty in *x86_pkg_temp* | *coretemp* | *k10temp* | *tctl* | *cpu*)
|
|
||||||
if [ -z "$cpu_zone" ] || [ "$t" -gt "$cpu_zone" ]; then cpu_zone=$t; fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
if [ -z "$any_zone" ] || [ "$t" -gt "$any_zone" ]; then any_zone=$t; fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$cpu_zone" ]; then
|
|
||||||
for d in /sys/class/hwmon/hwmon*/; do
|
|
||||||
[ -r "${d}name" ] || continue
|
|
||||||
case $(<"${d}name") in coretemp | k10temp | zenpower | cpu_thermal)
|
|
||||||
for f in "${d}"temp*_input; do
|
|
||||||
[ -r "$f" ] || continue
|
|
||||||
t=$(<"$f")
|
|
||||||
case $t in '' | *[!0-9]*) continue ;; esac
|
|
||||||
if [ -z "$hw" ] || [ "$t" -gt "$hw" ]; then hw=$t; fi
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
t=${cpu_zone:-${hw:-$any_zone}}
|
|
||||||
[ -n "$t" ] || exit 0
|
|
||||||
printf '\U000f050f %d° ' $(((t + 500) / 1000))
|
|
||||||
@@ -2,13 +2,21 @@
|
|||||||
# Without this, tmux collapses them to a plain Enter. Requires tmux 3.5+ for csi-u.
|
# Without this, tmux collapses them to a plain Enter. Requires tmux 3.5+ for csi-u.
|
||||||
set -g extended-keys on
|
set -g extended-keys on
|
||||||
set -g extended-keys-format csi-u
|
set -g extended-keys-format csi-u
|
||||||
|
# termux reports xterm-256color: without RGB every #rrggbb below is approximated to the 256
|
||||||
|
# palette on the phone, and without extkeys tmux never asks the terminal for Shift/Ctrl+Enter.
|
||||||
|
# (kitty is xterm-kitty, whose terminfo already carries both.)
|
||||||
|
set -as terminal-features 'xterm-256color:RGB:extkeys'
|
||||||
|
# Let apps wrap escape sequences in DCS tmux passthrough (Claude Code / pi notifications,
|
||||||
|
# Claude's progress bar) so they reach the outer terminal instead of being swallowed.
|
||||||
|
set -g allow-passthrough on
|
||||||
|
|
||||||
# Mouse/touch reporting. Chiefly so the window list in the status bar is
|
# Mouse/touch reporting. Chiefly so the window list in the status bar is
|
||||||
# tappable — tmux binds MouseDown1Status to `switch-client -t =` out of the box,
|
# tappable — tmux binds MouseDown1Status to `switch-client -t =` out of the box,
|
||||||
# and status-format[1] below keeps the `range=window|…` markers that make each
|
# and status-format[1] below keeps the `range=window|…` markers that make each
|
||||||
# entry a hit target, so a tap on a tab switches to it with no extra binding.
|
# entry a hit target, so a tap on a tab switches to it with no extra binding.
|
||||||
# Wheel over the bar cycles windows; right-click a tab or the session name for
|
# Wheel over the bar cycles windows; right-click a tab or the session name for
|
||||||
# tmux's built-in menus (no touch equivalent — a phone can't right-click).
|
# tmux's built-in menus; tmux-menus (plugin below) puts the same menus on prefix+\,
|
||||||
|
# which a phone can tap through.
|
||||||
#
|
#
|
||||||
# The cost: drag-select now goes to tmux instead of kitty, so hold Shift when you
|
# The cost: drag-select now goes to tmux instead of kitty, so hold Shift when you
|
||||||
# want kitty's own selection. Scroll wheel enters copy-mode on the pane rather
|
# want kitty's own selection. Scroll wheel enters copy-mode on the pane rather
|
||||||
@@ -21,6 +29,18 @@ set -g base-index 1
|
|||||||
setw -g pane-base-index 1
|
setw -g pane-base-index 1
|
||||||
set -g renumber-windows on
|
set -g renumber-windows on
|
||||||
|
|
||||||
|
# One long agent turn scrolls past tmux's default 2000 lines; resurrect captures pane
|
||||||
|
# contents from this buffer too. Applies to panes created after the change.
|
||||||
|
set -g history-limit 50000
|
||||||
|
|
||||||
|
# Copy-mode: vi keys, v/y like an editor. set-clipboard on pushes every copy — tmux's own and
|
||||||
|
# those apps inside make (nvim, pi) — over OSC 52 to the CLIENT terminal, so a yank on the
|
||||||
|
# phone lands on the phone and on kitty lands on the desktop, wherever the server runs.
|
||||||
|
setw -g mode-keys vi
|
||||||
|
bind -T copy-mode-vi v send -X begin-selection
|
||||||
|
bind -T copy-mode-vi y send -X copy-selection-and-cancel
|
||||||
|
set -s set-clipboard on
|
||||||
|
|
||||||
# --- Session persistence (survives reboot) ---------------------------------
|
# --- Session persistence (survives reboot) ---------------------------------
|
||||||
# resurrect: save/restore sessions, windows, panes, layout and working dirs
|
# resurrect: save/restore sessions, windows, panes, layout and working dirs
|
||||||
# (prefix + Ctrl-s to save, prefix + Ctrl-r to restore).
|
# (prefix + Ctrl-s to save, prefix + Ctrl-r to restore).
|
||||||
@@ -29,6 +49,8 @@ set -g renumber-windows on
|
|||||||
set -g @plugin 'tmux-plugins/tpm'
|
set -g @plugin 'tmux-plugins/tpm'
|
||||||
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
||||||
set -g @plugin 'tmux-plugins/tmux-continuum'
|
set -g @plugin 'tmux-plugins/tmux-continuum'
|
||||||
|
# menus: tmux's context menus on prefix+\ (tappable; no right-click needed on a phone).
|
||||||
|
set -g @plugin 'jaclu/tmux-menus'
|
||||||
|
|
||||||
# Also capture pane scrollback and restore running programs (nvim, …).
|
# Also capture pane scrollback and restore running programs (nvim, …).
|
||||||
# nvim 'session' strategy restores via Session.vim when present, else plain nvim.
|
# nvim 'session' strategy restores via Session.vim when present, else plain nvim.
|
||||||
@@ -87,20 +109,23 @@ set -g status-left "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@left_ful
|
|||||||
|
|
||||||
# Middle: flat window list — inactive muted, active mauve/bold, 2-space gaps.
|
# Middle: flat window list — inactive muted, active mauve/bold, 2-space gaps.
|
||||||
set -g window-status-separator " "
|
set -g window-status-separator " "
|
||||||
setw -g window-status-format "#[fg=#7f849c]#I #[fg=#6c7086]#W"
|
setw -g window-status-format "#[fg=#7f849c]#I#{P:#{E:@agent_badge}} #[fg=#6c7086]#W#[default]"
|
||||||
setw -g window-status-current-format "#[fg=#cba6f7,bold]#I #[fg=#cdd6f4,bold]#W"
|
setw -g window-status-current-format "#[fg=#cba6f7,bold]#I #[fg=#cdd6f4,bold]#W"
|
||||||
setw -g window-status-activity-style "fg=#f9e2af"
|
setw -g window-status-activity-style "fg=#f9e2af"
|
||||||
|
# The agent-state bell flags the window (monitor-bell) so the outer terminal buzzes; tmux's
|
||||||
|
# default bell style is `reverse`, which painted the window entry — only the glyph gets colour.
|
||||||
|
setw -g window-status-bell-style default
|
||||||
|
|
||||||
# Right: net dl/ul · cpu · mem │ date │ clock. continuum prepends its invisible
|
# Right: net dl/ul · cpu · mem · temp │ date │ clock. One #() job per refresh: metrics.sh
|
||||||
# save hook to status-right (this block sits above the tpm `run` line, so the
|
# prints the whole coloured segment (it was five separate scripts per tick). continuum
|
||||||
# hook survives every reload). Metrics are scripts/*.sh, symlinked into
|
# prepends its invisible save hook to status-right (this block sits above the tpm `run`
|
||||||
# ~/.config/tmux/scripts/. The hook lands ahead of the #{?} below, i.e. outside
|
# line, so the hook survives every reload). The hook lands ahead of the #{?} below, i.e.
|
||||||
# it, so it keeps running for every client regardless of which variant is drawn.
|
# outside it, so it keeps running for every client regardless of which variant is drawn.
|
||||||
set -g @right_full "#[fg=#a6e3a1]#(~/.config/tmux/scripts/net.sh dl) #[fg=#f9e2af]#(~/.config/tmux/scripts/net.sh ul) #[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) #[fg=#fab387]#(~/.config/tmux/scripts/temp.sh)#[fg=#45475a]│ #[fg=#a6adc8]%a %b %-d #[fg=#45475a]│ #[fg=#cba6f7,bold]%H:%M "
|
set -g @right_full "#(~/.config/tmux/scripts/metrics.sh full)#[fg=#45475a]│ #[fg=#a6adc8]%a %b %-d #[fg=#45475a]│ #[fg=#cba6f7,bold]%H:%M "
|
||||||
|
|
||||||
# Compact: cpu + mem only. Net rates and the temp glyph go first (widest, least
|
# Compact: cpu + mem only. Net rates and the temp glyph go first (widest, least
|
||||||
# useful on a phone), then the date, then the clock — a phone shows its own.
|
# useful on a phone), then the date, then the clock — a phone shows its own.
|
||||||
set -g @right_compact "#[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) "
|
set -g @right_compact "#(~/.config/tmux/scripts/metrics.sh compact) "
|
||||||
|
|
||||||
set -g status-right "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@right_full},#{E:@right_compact}}"
|
set -g status-right "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@right_full},#{E:@right_compact}}"
|
||||||
|
|
||||||
@@ -125,28 +150,48 @@ bind S {
|
|||||||
# Two rows: [0] drawn top-border rule · [1] the bar content. The rule doubles as
|
# Two rows: [0] drawn top-border rule · [1] the bar content. The rule doubles as
|
||||||
# the separator from the pane content above; tmux status height is whole rows, so
|
# the separator from the pane content above; tmux status height is whole rows, so
|
||||||
# one row above the content is the smallest gap that still shows a border.
|
# one row above the content is the smallest gap that still shows a border.
|
||||||
# Full-width run of ─ (yes|head|paste avoids %, backslashes and nested parens so
|
# The rule is a literal run of ─ in @rule (300 columns; tmux clips it to the client
|
||||||
# tmux's strftime/quote/#() parsing leave it alone); tmux truncates it to width.
|
# width), so drawing it costs no process.
|
||||||
# status-format[1] is tmux 3.7's default content template (status-left/windows/right).
|
# status-format[1] is tmux 3.7's default content template (status-left/windows/right).
|
||||||
set -g status 2
|
set -g status 2
|
||||||
set -g status-format[0] "#[fg=#6c7086,bg=default]#(yes ─ | head -n 400 | paste -sd '')"
|
set -g @rule "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||||
set -g status-format[1] "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?loop_last_flag,,#{E:window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?loop_last_flag,,#{E:window-status-separator}}}#[range=user|newwin] #[fg=#6c7086]+#[default] #[norange]#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]"
|
set -g status-format[0] "#[fg=#6c7086,bg=default]#{@rule}"
|
||||||
|
set -g status-format[1] "#[align=left range=left #{E:status-left-style}]#[push-default]#{T;=/#{status-left-length}:status-left}#[pop-default]#[norange default]#[list=on align=#{status-justify}]#[list=left-marker]<#[list=right-marker]>#[list=on]#{W:#[range=window|#{window_index} #{E:window-status-style}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-format}#[pop-default]#[norange default]#{?loop_last_flag,,#{E:window-status-separator}},#[range=window|#{window_index} list=focus #{?#{!=:#{E:window-status-current-style},default},#{E:window-status-current-style},#{E:window-status-style}}#{?#{&&:#{window_last_flag},#{!=:#{E:window-status-last-style},default}}, #{E:window-status-last-style},}#{?#{&&:#{window_bell_flag},#{!=:#{E:window-status-bell-style},default}}, #{E:window-status-bell-style},#{?#{&&:#{||:#{window_activity_flag},#{window_silence_flag}},#{!=:#{E:window-status-activity-style},default}}, #{E:window-status-activity-style},}}]#[push-default]#{T:window-status-current-format}#[pop-default]#[norange list=on default]#{?loop_last_flag,,#{E:window-status-separator}}}#[range=user|newwin] #[fg=#6c7086]+#[default] #[norange]#[range=user|agentnext] #[fg=#6c7086]»#[default] #[norange]#[nolist align=right range=right #{E:status-right-style}]#[push-default]#{T;=/#{status-right-length}:status-right}#[pop-default]#[norange default]"
|
||||||
|
|
||||||
# The `+` appended to the window list above sits in a `range=user|newwin`
|
# The `+` appended to the window list above sits in a `range=user|newwin`
|
||||||
# marker. Per tmux(1), a user range triggers the same `Status` mouse key as a
|
# marker. Per tmux(1), a user range triggers the same `Status` mouse key as a
|
||||||
# window range and exposes its argument in #{mouse_status_range} — window ranges
|
# window range and exposes its argument in #{mouse_status_range} — window ranges
|
||||||
# report the literal string "window" — so one binding can serve both: click a
|
# report the literal string "window" — so one binding can serve both: click a
|
||||||
# tab to select it (tmux's stock behaviour, kept in the else branch), click the
|
# tab to select it (tmux's stock behaviour, kept in the else branch), click the
|
||||||
# `+` to open a new window. Padded with spaces on both sides so it's a wide
|
# `+` to open a new window, click the `»` to jump to the next window with an agent
|
||||||
# enough target to hit with a thumb.
|
# mark (scripts/agent-next.sh, also prefix+Tab). Padded with spaces on both sides so
|
||||||
|
# each is a wide enough target to hit with a thumb.
|
||||||
bind -n MouseDown1Status {
|
bind -n MouseDown1Status {
|
||||||
if -F '#{==:#{mouse_status_range},newwin}' {
|
if -F '#{==:#{mouse_status_range},newwin}' {
|
||||||
new-window
|
new-window
|
||||||
|
} {
|
||||||
|
if -F '#{==:#{mouse_status_range},agentnext}' {
|
||||||
|
run-shell '~/.config/tmux/scripts/agent-next.sh'
|
||||||
} {
|
} {
|
||||||
switch-client -t =
|
switch-client -t =
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Agent state per pane — set by ~/.config/tmux/scripts/agent-state.sh <tool> <state> from the
|
||||||
|
# Claude Code / codex / pi / opencode hooks: @agent = busy|done|input|error. @agent_badge draws
|
||||||
|
# one glyph glued to the window number ("2✓ name") in the window list, the same for every tool: ✓ done (green),
|
||||||
|
# ? waiting for input (yellow), ! error (red); busy draws nothing. Visiting the pane only takes
|
||||||
|
# the colour away (pane-focus-in sets @agent_seen; needs focus-events): the glyph stays, grey,
|
||||||
|
# until the next prompt turns the state busy. prefix K clears every mark; prefix Tab (or the »
|
||||||
|
# in the bar) jumps to the next window with a mark, coloured ones first. A coloured glyph
|
||||||
|
# always means "this happened while you were not looking".
|
||||||
|
set -g @agent_badge "#{?#{==:#{@agent},done},#{?#{@agent_seen},#[fg=#6c7086],#[fg=#a6e3a1]}✓,#{?#{==:#{@agent},input},#{?#{@agent_seen},#[fg=#6c7086],#[fg=#f9e2af]}?,#{?#{==:#{@agent},error},#{?#{@agent_seen},#[fg=#6c7086],#[fg=#f38ba8]}!,}}}"
|
||||||
|
set -g focus-events on
|
||||||
|
set-hook -g pane-focus-in 'set -p @agent_seen 1'
|
||||||
|
bind Tab run-shell '~/.config/tmux/scripts/agent-next.sh'
|
||||||
|
bind K run-shell 'for p in $(tmux list-panes -a -F "##{pane_id}"); do tmux set -p -t $p -u @agent; tmux set -p -t $p -u @agent_seen; done' \; display-message "agent marks cleared"
|
||||||
|
|
||||||
# Round out the theme so no default green leaks into borders/messages/modes.
|
# Round out the theme so no default green leaks into borders/messages/modes.
|
||||||
set -g pane-border-style "fg=#313244"
|
set -g pane-border-style "fg=#313244"
|
||||||
set -g pane-active-border-style "fg=#cba6f7"
|
set -g pane-active-border-style "fg=#cba6f7"
|
||||||
|
|||||||
@@ -59,10 +59,11 @@ have a slash command to disable them for the current session without deleting.
|
|||||||
| `vim-toggle.ts` | `/vim` — drops the modal editor for the rest of the session, and brings it back via `ctx.reload()`. Pairs with the **`pi-vim` package** (below), which registers no command of its own | **custom** | delete file → you lose the toggle, not the editor; remove the package to drop modal editing itself |
|
| `vim-toggle.ts` | `/vim` — drops the modal editor for the rest of the session, and brings it back via `ctx.reload()`. Pairs with the **`pi-vim` package** (below), which registers no command of its own | **custom** | delete file → you lose the toggle, not the editor; remove the package to drop modal editing itself |
|
||||||
| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it, `/plan-done` clears a stuck progress checklist | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) |
|
| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it, `/plan-done` clears a stuck progress checklist | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) |
|
||||||
| `notify.ts` | Native terminal notification (OSC 777/99, Windows toast) when the agent goes idle | **verbatim** from pi's `examples/extensions/notify.ts` | delete file |
|
| `notify.ts` | Native terminal notification (OSC 777/99, Windows toast) when the agent goes idle | **verbatim** from pi's `examples/extensions/notify.ts` | delete file |
|
||||||
|
| `agent-state.ts` | Mirrors Pi's state (`agent_start` busy, `agent_settled` done, `ui_prompt_*` input, `session_shutdown` off) into the tmux pane it runs in via `~/.config/tmux/scripts/agent-state.sh`, for the status-bar agent badges; no-op outside tmux | own | delete file |
|
||||||
| `questionnaire.ts` | Interactive multi-question overlay the model can call as a tool | **near-verbatim** from pi's `examples/extensions/questionnaire.ts` (one-line fix) | delete file |
|
| `questionnaire.ts` | Interactive multi-question overlay the model can call as a tool | **near-verbatim** from pi's `examples/extensions/questionnaire.ts` (one-line fix) | delete file |
|
||||||
| `permission-gate.ts` | Prompts before dangerous `bash` commands (blocks outright with no UI). Local list covers git history rewrites, package managers, `systemctl` and irreversible disk ops on top of upstream's `rm -rf`/`sudo`/`chmod 777` | **adapted** from pi's `examples/extensions/permission-gate.ts` | delete file |
|
| `permission-gate.ts` | Prompts before dangerous `bash` commands (blocks outright with no UI). Local list covers git history rewrites, package managers, `systemctl` and irreversible disk ops on top of upstream's `rm -rf`/`sudo`/`chmod 777` | **adapted** from pi's `examples/extensions/permission-gate.ts` | delete file |
|
||||||
| `protected-paths.ts` | Blocks `write`/`edit` to secrets and repo plumbing (`secrets.fish`, `auth.json`, `.env`, `.ssh/`, `.git/`, …) | **adapted** from pi's `examples/extensions/protected-paths.ts` | delete file |
|
| `protected-paths.ts` | Blocks `write`/`edit` to secrets and repo plumbing (`secrets.fish`, `auth.json`, `.env`, `.ssh/`, `.git/`, …) | **adapted** from pi's `examples/extensions/protected-paths.ts` | delete file |
|
||||||
| `compaction-watch.ts` | Surfaces every compaction and its `reason`; loudly flags `overflow` (recovery rather than prevention), which means `reserveTokens` is below the model's `maxTokens` | **custom** | delete file |
|
| `compaction-watch.ts` | Surfaces every compaction and its `reason`; on `overflow` (recovery rather than prevention) it reads the turn that caused it and says which kind it was, with numbers — a reply pi itself had clamped (`max_tokens` = window − estimate − 4096, the usual one on thinking models) or a prompt the server really rejected | **custom** | delete file |
|
||||||
| `verbose.ts` | `/verbose [on|off|status]` and **Alt+V** toggle the *persisted* verbose default (`hideThinkingBlock`, `showCacheMissNotices`) and report pi's live keys — `ctrl+o` tool output, `ctrl+t` thinking, which an extension cannot drive itself | **custom** | delete file; the live keys are built in and keep working |
|
| `verbose.ts` | `/verbose [on|off|status]` and **Alt+V** toggle the *persisted* verbose default (`hideThinkingBlock`, `showCacheMissNotices`) and report pi's live keys — `ctrl+o` tool output, `ctrl+t` thinking, which an extension cannot drive itself | **custom** | delete file; the live keys are built in and keep working |
|
||||||
| `confirm-destructive.ts` | Confirms before session clear / switch / fork | **verbatim** from pi's `examples/extensions/confirm-destructive.ts` | delete file |
|
| `confirm-destructive.ts` | Confirms before session clear / switch / fork | **verbatim** from pi's `examples/extensions/confirm-destructive.ts` | delete file |
|
||||||
| `session-name.ts` | `/session-name [name]` — friendly names in the session picker instead of the first message | **verbatim** from pi's `examples/extensions/session-name.ts` | delete file |
|
| `session-name.ts` | `/session-name [name]` — friendly names in the session picker instead of the first message | **verbatim** from pi's `examples/extensions/session-name.ts` | delete file |
|
||||||
@@ -117,7 +118,9 @@ pi install npm:pi-vim
|
|||||||
> caret range would quietly drift off `pi --version`.
|
> caret range would quietly drift off `pi --version`.
|
||||||
|
|
||||||
Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI),
|
Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI),
|
||||||
`borderSync`/`labelSync` pinned so only the label is tinted, and
|
`borderSync` so the input border itself takes the mode color in NORMAL/VISUAL
|
||||||
|
(mauve/yellow) and is left to pi in INSERT/EX, `labelSync` so the label always carries
|
||||||
|
its mode color, and
|
||||||
`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every
|
`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every
|
||||||
`dd`/`x` would clobber the OS clipboard. `exCommand.copyInputToClipboard` is left
|
`dd`/`x` would clobber the OS clipboard. `exCommand.copyInputToClipboard` is left
|
||||||
**off**: it copies the composed prompt out, so upstream only honours it from the
|
**off**: it copies the composed prompt out, so upstream only honours it from the
|
||||||
@@ -171,14 +174,14 @@ Beyond pointing at local models, these values deviate from pi's defaults:
|
|||||||
|
|
||||||
| Key | Value here | Stock default | Why |
|
| Key | Value here | Stock default | Why |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k–128k), but **≥ every model's `maxTokens`** — otherwise a turn can overflow the window before compaction even fires |
|
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k–128k), but **≥ every model's `maxTokens` + 4096**: pi clamps each request's `max_tokens` to `contextWindow − estimate − 4096`, and with 24k/4096 that clamp starts exactly at this threshold (see CLAUDE.md for the `overflow` recoveries it can still produce) |
|
||||||
| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one |
|
| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one |
|
||||||
| `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm |
|
| `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm |
|
||||||
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
|
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
|
||||||
| `defaultThinkingLevel` | `medium` | — | the default model is a thinking preset (`Qwen3.6-35B-A3B-Thinking`); Alt+T cycles it per session |
|
| `defaultThinkingLevel` | `medium` | — | the default model is a thinking preset (`Qwen3.6-35B-A3B-Thinking`); Alt+T cycles it per session |
|
||||||
| `thinkingBudgets` | `512 / 1024 / 2048 / 3072` (minimal / low / medium / high) | 1024 / 2048 / 8192 / 16384 | per-request cap on the `<think>` block, sent only for models whose `compat.thinkingTokenBudgetField` is set (`Ornith`, as `thinking_budget_tokens`). pi clamps the budget to `maxTokens − 1024`, so on a 4096-token reply the stock values collapse to 3072 and leave the answer 1024 tokens — the recipe for "Response was truncated before completion." |
|
| `thinkingBudgets` | `512 / 1024 / 2048 / 3072` (minimal / low / medium / high) | 1024 / 2048 / 8192 / 16384 | per-request cap on the `<think>` block, sent only for models whose `compat.thinkingTokenBudgetField` is set (`Ornith`, as `thinking_budget_tokens`). pi clamps the budget to `maxTokens − 1024`, so on a 4096-token reply the stock values collapse to 3072 and leave the answer 1024 tokens — the recipe for "Response was truncated before completion." |
|
||||||
| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above |
|
| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above |
|
||||||
| `piVim` | mode colors, `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
|
| `piVim` | mode colors, border painted per mode (NORMAL/VISUAL), `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
|
||||||
| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen |
|
| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen |
|
||||||
|
|
||||||
**Remove:** delete each key to fall back to pi's default (or drop the whole
|
**Remove:** delete each key to fall back to pi's default (or drop the whole
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Pi agent-state extension: mirrors Pi's state into the tmux pane it runs in via
|
||||||
|
* ~/.config/tmux/scripts/agent-state.sh (pi busy|done|input|off), so the tmux status bar
|
||||||
|
* shows which pane is done or waiting for you. The script is a no-op outside tmux.
|
||||||
|
*/
|
||||||
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
|
import { execFile } from "node:child_process";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
const SCRIPT = join(homedir(), ".config", "tmux", "scripts", "agent-state.sh");
|
||||||
|
|
||||||
|
function state(s: "busy" | "done" | "input" | "error" | "off"): void {
|
||||||
|
if (!process.env.TMUX_PANE) return;
|
||||||
|
execFile(SCRIPT, ["pi", s], () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.on("agent_start", async () => state("busy"));
|
||||||
|
// agent_settled, not agent_end: Pi may still retry, compact, or run queued follow-ups.
|
||||||
|
pi.on("agent_settled", async () => state("done"));
|
||||||
|
// Blocking confirm/select prompts from extensions (permission-gate, confirm-destructive, questionnaire).
|
||||||
|
pi.on("ui_prompt_start", async () => state("input"));
|
||||||
|
pi.on("ui_prompt_end", async () => state("busy"));
|
||||||
|
pi.on("session_shutdown", async () => state("off"));
|
||||||
|
}
|
||||||
@@ -3,31 +3,94 @@
|
|||||||
*
|
*
|
||||||
* Compaction is otherwise silent, which is why "compacting fails a lot" took code
|
* Compaction is otherwise silent, which is why "compacting fails a lot" took code
|
||||||
* reading and arithmetic to diagnose rather than a glance at a log. This makes it
|
* reading and arithmetic to diagnose rather than a glance at a log. This makes it
|
||||||
* visible, and specifically names the failure mode that was breaking it here before
|
* visible and, for the one reason that signals trouble, says WHICH trouble.
|
||||||
* 2026-08-16.
|
|
||||||
*
|
*
|
||||||
* pi reports a `reason` on every compaction:
|
* pi reports a `reason` on every compaction:
|
||||||
* "manual" - you ran /compact
|
* "manual" - you ran /compact
|
||||||
* "threshold" - the healthy path: contextTokens passed contextWindow - reserveTokens
|
* "threshold" - the healthy path: contextTokens passed contextWindow - reserveTokens
|
||||||
* "overflow" - the window was ALREADY full; this is recovery, not prevention
|
* "overflow" - recovery, not prevention: the previous assistant turn ended badly,
|
||||||
|
* pi compacts and (when `willRetry`) re-runs that turn once
|
||||||
*
|
*
|
||||||
* An "overflow" compaction means the turn blew past contextWindow before the threshold
|
* Since pi 0.84.3 "overflow" covers TWO different situations, and the numbers on the
|
||||||
* could fire, which happens whenever `compaction.reserveTokens` is smaller than the
|
* last assistant message tell them apart (CLAUDE.md > Compaction tuning has the
|
||||||
* model's `maxTokens`. pi then gets one recovery attempt, summarizing from an
|
* arithmetic for both):
|
||||||
* already-overflowed context — and when that does not fit either, the session dies with
|
|
||||||
* "Context overflow recovery failed after one compact-and-retry attempt".
|
|
||||||
*
|
*
|
||||||
* So: seeing "threshold" is fine. Seeing "overflow" means the settings are wrong for the
|
* 1. A real overflow — the server rejected the prompt (stopReason "error" matching
|
||||||
* model in use, and this says so at the moment it happens instead of leaving you to
|
* pi's overflow patterns), or reported more input than the declared window.
|
||||||
* infer it later. See CLAUDE.md > Compaction tuning.
|
* Fix: contextWindow / reserveTokens / maxTokens are wrong for the model in use.
|
||||||
|
* 2. Truncated-reply recovery — the reply stopped with stopReason "length" while
|
||||||
|
* usage.output is BELOW the model's maxTokens. The request itself carried a smaller
|
||||||
|
* max_tokens: before every call pi-ai clamps it to
|
||||||
|
* `contextWindow − its own token estimate − 4096` (clampMaxTokensToContext,
|
||||||
|
* CONTEXT_SAFETY_TOKENS) and then shrinks the thinking budget so that only 1024
|
||||||
|
* answer tokens remain. The window was never full. On a 24k window with maxTokens
|
||||||
|
* 4096 the clamp bites at any estimate above 16384 — the same number as the
|
||||||
|
* compaction threshold with reserveTokens 8192 — so it hits in the gaps the threshold
|
||||||
|
* check never sees: the first request after a compaction (no server usage to anchor
|
||||||
|
* on, so the system prompt and the tool schemas join the estimate) and the first call
|
||||||
|
* of a user turn (the new message). Thinking models feel it first: 2048 of thinking
|
||||||
|
* leaves a clamped reply about 1024 tokens of answer, and a `write` payload is cut.
|
||||||
|
*
|
||||||
|
* The old message here blamed every "overflow" on `reserveTokens < maxTokens`, which is
|
||||||
|
* wrong for case 2 — the one that actually shows up (Ornith, 2026-10-22). Now the
|
||||||
|
* notification names the case and prints the numbers (output vs maxTokens, context vs
|
||||||
|
* window, the estimate the clamp implies), so the next occurrence is diagnosable from the
|
||||||
|
* screen instead of from a session file.
|
||||||
*
|
*
|
||||||
* Note there is no event for a *failed* auto-compaction — `onError` exists only on the
|
* Note there is no event for a *failed* auto-compaction — `onError` exists only on the
|
||||||
* manual `ctx.compact()` call — so catching the overflow entry is the earliest reliable
|
* manual `ctx.compact()` call — so catching the overflow entry is the earliest reliable
|
||||||
* warning available.
|
* warning available. Observe-only: it never alters the compaction itself.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
|
|
||||||
|
/** pi-ai's CONTEXT_SAFETY_TOKENS: the margin clampMaxTokensToContext keeps below the window. */
|
||||||
|
const PI_CONTEXT_SAFETY_TOKENS = 4096;
|
||||||
|
|
||||||
|
/** Last assistant message on the branch — the turn that triggered the recovery. */
|
||||||
|
function lastAssistant(entries: readonly unknown[]): AssistantMessage | undefined {
|
||||||
|
for (let i = entries.length - 1; i >= 0; i--) {
|
||||||
|
const e = entries[i] as { type?: string; message?: { role?: string } };
|
||||||
|
if (e.type === "message" && e.message?.role === "assistant") return e.message as AssistantMessage;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One line saying which kind of "overflow" this is, with the numbers that prove it. */
|
||||||
|
export function describeOverflow(last: AssistantMessage | undefined, window: number, maxTokens: number): string {
|
||||||
|
if (!last) return "no assistant message on the branch to inspect.";
|
||||||
|
const u = last.usage;
|
||||||
|
const output = u?.output ?? 0;
|
||||||
|
const context = (u?.input ?? 0) + (u?.cacheRead ?? 0) + (u?.cacheWrite ?? 0) + output;
|
||||||
|
const ctxStr = window > 0 ? `${context}/${window}` : `${context}`;
|
||||||
|
const checkWindow = "check contextWindow against the server ctx-size (CLAUDE.md > Compaction tuning).";
|
||||||
|
|
||||||
|
if (last.stopReason === "error") {
|
||||||
|
return `the server rejected the request at context ${ctxStr} (${last.errorMessage ?? "no message"}) — a real overflow; ${checkWindow}`;
|
||||||
|
}
|
||||||
|
if (last.stopReason === "length") {
|
||||||
|
if (output === 0 && window > 0 && context >= window * 0.99) {
|
||||||
|
return `the server produced no tokens at context ${ctxStr} — the prompt alone filled the window; ${checkWindow}`;
|
||||||
|
}
|
||||||
|
if (window > 0 && context >= window - 64) {
|
||||||
|
return (
|
||||||
|
`the server ran out of context mid-reply (${ctxStr}, output ${output}/${maxTokens}) — the prompt was ` +
|
||||||
|
`under-estimated (big tool results or images count more than chars ÷ 4); not a reserveTokens problem.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (maxTokens > 0 && output < maxTokens) {
|
||||||
|
const estimate = window > 0 ? ` pi's estimate at request time ≈ ${window - PI_CONTEXT_SAFETY_TOKENS - output}, server-side context ${ctxStr}.` : "";
|
||||||
|
return (
|
||||||
|
`truncated-reply recovery, NOT a full window: the reply stopped at ${output} of ${maxTokens} max tokens, ` +
|
||||||
|
`so pi had clamped max_tokens to window − estimate − ${PI_CONTEXT_SAFETY_TOKENS}.${estimate} ` +
|
||||||
|
`reserveTokens is not the knob — see CLAUDE.md > Compaction tuning > max_tokens clamp.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `stopReason ${last.stopReason}, context ${ctxStr}, output ${output}/${maxTokens} — input reported above the declared window; ${checkWindow}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
let overflowCount = 0;
|
let overflowCount = 0;
|
||||||
|
|
||||||
@@ -36,12 +99,9 @@ export default function (pi: ExtensionAPI) {
|
|||||||
|
|
||||||
if (event.reason === "overflow") {
|
if (event.reason === "overflow") {
|
||||||
overflowCount++;
|
overflowCount++;
|
||||||
ctx.ui.notify(
|
const model = ctx.model;
|
||||||
`Compacting in OVERFLOW recovery (#${overflowCount}) — the context window was already ` +
|
const verdict = describeOverflow(lastAssistant(event.branchEntries), model?.contextWindow ?? 0, model?.maxTokens ?? 0);
|
||||||
`full before compaction could fire. Raise compaction.reserveTokens above this model's ` +
|
ctx.ui.notify(`Compacting in OVERFLOW recovery (#${overflowCount}) — ${verdict}`, "warning");
|
||||||
`maxTokens, or cap maxTokens in models.json. See CLAUDE.md > Compaction tuning.`,
|
|
||||||
"warning",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never alter the compaction itself — observe only.
|
// Never alter the compaction itself — observe only.
|
||||||
|
|||||||
@@ -17,7 +17,13 @@
|
|||||||
* model model id + · thinking level (Claude 🤖) U+F06A9 nf-md-robot
|
* model model id + · thinking level (Claude 🤖) U+F06A9 nf-md-robot
|
||||||
* context % of context window used (+ tokens) (Claude 📝) U+F021A nf-md-text_box
|
* context % of context window used (+ tokens) (Claude 📝) U+F021A nf-md-text_box
|
||||||
* t/s last response's decode throughput U+F04C5 nf-md-speedometer
|
* t/s last response's decode throughput U+F04C5 nf-md-speedometer
|
||||||
* ↑ ↓ tokens session input / output tokens (Claude 💰) plain arrows
|
* (llama.cpp's own server-side number + dim ` pp N` prompt-processing
|
||||||
|
* speed when the final SSE chunk carries `timings`; otherwise the
|
||||||
|
* client-side clock -- see "server-side timings" below)
|
||||||
|
* ↑ ↓ tokens session prompt / output tokens (Claude 🔢) plain arrows
|
||||||
|
* (prompt = input + cache read + cache write: everything the server
|
||||||
|
* processed, matching the Claude bar — llama.cpp reports a cached
|
||||||
|
* prefix as cacheRead, so bare usage.input is a few dozen tokens)
|
||||||
*
|
*
|
||||||
* git, context, t/s and tokens only appear once there is data for them (i.e. after
|
* git, context, t/s and tokens only appear once there is data for them (i.e. after
|
||||||
* the first response, and git only inside a git repo). Extension statuses (e.g.
|
* the first response, and git only inside a git repo). Extension statuses (e.g.
|
||||||
@@ -58,12 +64,111 @@ const CTX_WARN_AT = 80;
|
|||||||
// direct access to it). Defaults to the settings.json default of "off".
|
// direct access to it). Defaults to the settings.json default of "off".
|
||||||
let thinkingLevel = "off";
|
let thinkingLevel = "off";
|
||||||
|
|
||||||
// Decode throughput (tokens/sec) of the most recent assistant response, measured
|
// Decode throughput (tokens/sec) of the most recent assistant response. Preferred
|
||||||
// from the first streamed token to message end so prompt-eval time is excluded.
|
// source: the server's own timings (below). Fallback: a client-side clock from the
|
||||||
|
// first streamed token to message end, so prompt-eval time is excluded.
|
||||||
let lastTps = 0;
|
let lastTps = 0;
|
||||||
|
let lastPp = 0; // prompt-processing t/s, server-side only (0 = unknown)
|
||||||
let genFirstTokenAt = 0;
|
let genFirstTokenAt = 0;
|
||||||
let sawToken = false;
|
let sawToken = false;
|
||||||
|
|
||||||
|
// --- server-side timings (llama.cpp) ----------------------------------------
|
||||||
|
// llama.cpp puts a `timings` object on the final SSE chunk (the one carrying `usage`):
|
||||||
|
// prompt_n / prompt_ms / prompt_per_second, predicted_n / predicted_ms /
|
||||||
|
// predicted_per_second, cache_n, draft_n / draft_n_accepted. pi-ai's openai-completions
|
||||||
|
// provider reads only `usage` and drops the rest, and the extension API has no HTTP
|
||||||
|
// hook, so the one way to see it is to wrap globalThis.fetch and tee the SSE body.
|
||||||
|
// That is sanctioned: pi's http-dispatcher installs undici's fetch at startup and
|
||||||
|
// deliberately keeps a later override ("If a caller replaced fetch after module load,
|
||||||
|
// preserve that deliberate override"), and pi-ai builds `new OpenAI({ fetch: undefined })`
|
||||||
|
// per request, which falls back to globalThis.fetch -- so a wrapper installed at
|
||||||
|
// session_start sees every chat completion. Only POST */chat/completions responses
|
||||||
|
// with a text/event-stream body are teed; other providers' streams carry no `timings`
|
||||||
|
// and are passed through untouched apart from the tee. The tap stays installed for the
|
||||||
|
// life of the process (`/statusbar` off just stops displaying it; `/reload` rebinds the
|
||||||
|
// sink instead of wrapping twice).
|
||||||
|
interface ServerTimings {
|
||||||
|
tps: number; // predicted_per_second
|
||||||
|
pp: number; // prompt_per_second
|
||||||
|
predictedN: number;
|
||||||
|
promptN: number;
|
||||||
|
cacheN: number;
|
||||||
|
}
|
||||||
|
// Latest timings seen on the wire, consumed by message_end. Paired with the message by
|
||||||
|
// predicted_n === usage.output, so a compaction/summary call's timings never get
|
||||||
|
// attributed to the next visible response.
|
||||||
|
let pendingServerTimings: ServerTimings | undefined;
|
||||||
|
|
||||||
|
const FETCH_TAP_KEY = "__piStatusbarFetchTap";
|
||||||
|
|
||||||
|
/** Read one branch of a teed SSE body and report every `timings` object it carries. */
|
||||||
|
async function scanSse(stream: ReadableStream<Uint8Array>, onTimings: (t: ServerTimings) => void): Promise<void> {
|
||||||
|
const reader = stream.getReader();
|
||||||
|
const dec = new TextDecoder();
|
||||||
|
let buf = "";
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
buf += dec.decode(value, { stream: true });
|
||||||
|
let nl: number;
|
||||||
|
while ((nl = buf.indexOf("\n")) >= 0) {
|
||||||
|
const line = buf.slice(0, nl).trim();
|
||||||
|
buf = buf.slice(nl + 1);
|
||||||
|
if (!line.startsWith("data:")) continue;
|
||||||
|
const payload = line.slice(5).trim();
|
||||||
|
if (!payload.includes('"timings"')) continue; // cheap pre-filter: parse only the final chunk
|
||||||
|
try {
|
||||||
|
const t = JSON.parse(payload)?.timings;
|
||||||
|
if (t && typeof t.predicted_per_second === "number") {
|
||||||
|
onTimings({
|
||||||
|
tps: t.predicted_per_second,
|
||||||
|
pp: typeof t.prompt_per_second === "number" ? t.prompt_per_second : 0,
|
||||||
|
predictedN: t.predicted_n ?? 0,
|
||||||
|
promptN: t.prompt_n ?? 0,
|
||||||
|
cacheN: t.cache_n ?? 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// not JSON: ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// aborted / cancelled stream: the primary branch reports that, we just stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrap globalThis.fetch once; on /reload only rebind the sink. */
|
||||||
|
function installFetchTap(): void {
|
||||||
|
const g = globalThis as unknown as Record<string, unknown>;
|
||||||
|
const sink = (t: ServerTimings) => {
|
||||||
|
pendingServerTimings = t;
|
||||||
|
};
|
||||||
|
const existing = g[FETCH_TAP_KEY] as { sink: (t: ServerTimings) => void } | undefined;
|
||||||
|
if (existing) {
|
||||||
|
existing.sink = sink;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tap = { sink };
|
||||||
|
g[FETCH_TAP_KEY] = tap;
|
||||||
|
const orig = globalThis.fetch;
|
||||||
|
globalThis.fetch = (async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
|
||||||
|
const res = await orig(input, init);
|
||||||
|
let url = "";
|
||||||
|
try {
|
||||||
|
url = typeof input === "string" ? input : input instanceof URL ? input.href : ((input as Request)?.url ?? "");
|
||||||
|
} catch {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (!url.endsWith("/chat/completions") || !res.body) return res;
|
||||||
|
if (!(res.headers.get("content-type") ?? "").includes("text/event-stream")) return res;
|
||||||
|
const [ours, theirs] = res.body.tee();
|
||||||
|
void scanSse(ours, (t) => tap.sink(t));
|
||||||
|
return new Response(theirs, res);
|
||||||
|
}) as typeof fetch;
|
||||||
|
}
|
||||||
|
|
||||||
// Set from inside the footer factory so events can trigger a re-render.
|
// Set from inside the footer factory so events can trigger a re-render.
|
||||||
let requestRender: (() => void) | undefined;
|
let requestRender: (() => void) | undefined;
|
||||||
|
|
||||||
@@ -74,12 +179,27 @@ function collapseHome(p: string): string {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 1234 -> 1.2k, 843000 -> 843k, 1234567 -> 1.2M (same boundaries as the Claude bar's
|
||||||
|
* fmt_tokens: 0.9995 / 9.95 so a value never prints as "10.0k" or "1000k"). */
|
||||||
function fmtTokens(n: number): string {
|
function fmtTokens(n: number): string {
|
||||||
return n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`;
|
for (const [unit, div] of [
|
||||||
|
["M", 1e6],
|
||||||
|
["k", 1e3],
|
||||||
|
] as const) {
|
||||||
|
const v = n / div;
|
||||||
|
if (v < 0.9995) continue;
|
||||||
|
return v >= 9.95 ? `${v.toFixed(0)}${unit}` : `${v.toFixed(1)}${unit}`;
|
||||||
|
}
|
||||||
|
return `${Math.round(n)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cumulative session tokens, plus the last request's input tokens (~ current
|
/** Cumulative session tokens, plus the last request's prompt size (~ current
|
||||||
* context-window fill). */
|
* context-window fill).
|
||||||
|
*
|
||||||
|
* Prompt = input + cacheRead + cacheWrite. pi-ai splits a cached prefix out of
|
||||||
|
* `usage.input` (llama.cpp too: the second call of a session reports e.g. input 20,
|
||||||
|
* cacheRead 2417), so summing bare `input` undercounted ↑ and -- worse -- made the
|
||||||
|
* context % and the ≥80% warning read near zero on every cached call. */
|
||||||
function tokenStats(ctx: ExtensionContext): {
|
function tokenStats(ctx: ExtensionContext): {
|
||||||
input: number;
|
input: number;
|
||||||
output: number;
|
output: number;
|
||||||
@@ -92,9 +212,10 @@ function tokenStats(ctx: ExtensionContext): {
|
|||||||
if (entry.type === "message" && entry.message.role === "assistant") {
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
||||||
const usage = (entry.message as AssistantMessage).usage;
|
const usage = (entry.message as AssistantMessage).usage;
|
||||||
if (!usage) continue; // aborted stream: no usage — a throwing render kills the TUI
|
if (!usage) continue; // aborted stream: no usage — a throwing render kills the TUI
|
||||||
input += usage.input;
|
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
||||||
output += usage.output;
|
input += prompt;
|
||||||
lastInput = usage.input;
|
output += usage.output ?? 0;
|
||||||
|
lastInput = prompt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { input, output, lastInput };
|
return { input, output, lastInput };
|
||||||
@@ -161,8 +282,19 @@ export default function (pi: ExtensionAPI) {
|
|||||||
pi.on("message_end", async (event, ctx) => {
|
pi.on("message_end", async (event, ctx) => {
|
||||||
if (event.message?.role !== "assistant") return;
|
if (event.message?.role !== "assistant") return;
|
||||||
const output = (event.message as AssistantMessage).usage?.output ?? 0;
|
const output = (event.message as AssistantMessage).usage?.output ?? 0;
|
||||||
|
// The tee reader and pi-ai consume the same final chunk; pi-ai's path to this
|
||||||
|
// event is longer, but give the reader one macrotask in case it is still behind.
|
||||||
|
if (!pendingServerTimings) await new Promise((r) => setTimeout(r, 20));
|
||||||
|
const srv = pendingServerTimings;
|
||||||
|
pendingServerTimings = undefined;
|
||||||
|
if (srv && srv.predictedN > 0 && (output === 0 || srv.predictedN === output)) {
|
||||||
|
lastTps = srv.tps;
|
||||||
|
lastPp = srv.pp;
|
||||||
|
} else {
|
||||||
const secs = genFirstTokenAt ? (Date.now() - genFirstTokenAt) / 1000 : 0;
|
const secs = genFirstTokenAt ? (Date.now() - genFirstTokenAt) / 1000 : 0;
|
||||||
if (secs > 0 && output > 0) lastTps = output / secs;
|
if (secs > 0 && output > 0) lastTps = output / secs;
|
||||||
|
lastPp = 0;
|
||||||
|
}
|
||||||
requestRender?.();
|
requestRender?.();
|
||||||
if (enabled) updateContextWidget(ctx);
|
if (enabled) updateContextWidget(ctx);
|
||||||
});
|
});
|
||||||
@@ -217,14 +349,15 @@ export default function (pi: ExtensionAPI) {
|
|||||||
parts.push(seg);
|
parts.push(seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tokens/sec of the last response
|
// tokens/sec of the last response (+ dim prompt-processing t/s when the
|
||||||
|
// server reported it)
|
||||||
if (lastTps > 0) {
|
if (lastTps > 0) {
|
||||||
parts.push(
|
let seg = theme.fg("accent", ICON.tps) + " " + theme.fg("text", lastTps.toFixed(1)) + theme.fg("dim", " t/s");
|
||||||
theme.fg("accent", ICON.tps) + " " + theme.fg("text", lastTps.toFixed(1)) + theme.fg("dim", " t/s"),
|
if (lastPp > 0) seg += theme.fg("dim", ` pp ${lastPp.toFixed(0)}`);
|
||||||
);
|
parts.push(seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ↑ ↓ cumulative session tokens (Claude's 💰 slot; local models are $0)
|
// ↑ ↓ cumulative session prompt / output tokens (Claude's 🔢 segment)
|
||||||
if (input || output) {
|
if (input || output) {
|
||||||
parts.push(theme.fg("dim", `${ICON.up}${fmtTokens(input)} ${ICON.down}${fmtTokens(output)}`));
|
parts.push(theme.fg("dim", `${ICON.up}${fmtTokens(input)} ${ICON.down}${fmtTokens(output)}`));
|
||||||
}
|
}
|
||||||
@@ -236,6 +369,10 @@ export default function (pi: ExtensionAPI) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
|
// Installed here, not at load time: pi's http-dispatcher only swaps in undici's
|
||||||
|
// fetch (idle timeout, proxy) while globalThis.fetch is still the original, so the
|
||||||
|
// tap must not get there first. By session_start, startup is done.
|
||||||
|
installFetchTap();
|
||||||
if (enabled) install(ctx);
|
if (enabled) install(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -107,11 +107,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
||||||
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
|
"name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s)",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
||||||
"input": ["text", "image"],
|
"input": ["text", "image"],
|
||||||
"contextWindow": 24576,
|
"contextWindow": 32768,
|
||||||
"maxTokens": 4096,
|
"maxTokens": 4096,
|
||||||
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
||||||
},
|
},
|
||||||
@@ -242,11 +242,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
|
||||||
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
|
"name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s)",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
|
||||||
"input": ["text", "image"],
|
"input": ["text", "image"],
|
||||||
"contextWindow": 24576,
|
"contextWindow": 32768,
|
||||||
"maxTokens": 4096,
|
"maxTokens": 4096,
|
||||||
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,8 +53,8 @@
|
|||||||
},
|
},
|
||||||
"borderSync": {
|
"borderSync": {
|
||||||
"insert": "host",
|
"insert": "host",
|
||||||
"normal": "host",
|
"normal": "mode",
|
||||||
"visual": "host",
|
"visual": "mode",
|
||||||
"ex": "host"
|
"ex": "host"
|
||||||
},
|
},
|
||||||
"labelSync": {
|
"labelSync": {
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"ex": "mode"
|
"ex": "mode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lastChangelogVersion": "1.0.0",
|
"lastChangelogVersion": "1.5.0",
|
||||||
"packages": [
|
"packages": [
|
||||||
"npm:pi-vim"
|
"npm:pi-vim"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Animations/
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = yes
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
animation = fade, 1, 10, default
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
|
|
||||||
# ░▒▒▒░░░░░▓▓ ___________
|
|
||||||
# ░░▒▒▒░░░░░▓▓ //___________/
|
|
||||||
# ░░▒▒▒░░░░░▓▓ _ _ _ _ _____
|
|
||||||
# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/
|
|
||||||
# ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___
|
|
||||||
# ░▒▒░░▓▓ ▓▓ \__ |____/ |____/
|
|
||||||
# ░▒▓▓ ▓▓ //____/
|
|
||||||
|
|
||||||
|
|
||||||
$scrPath = $HOME/.local/share/bin # set scripts path
|
|
||||||
|
|
||||||
|
|
||||||
# █▀▄▀█ █▀█ █▄░█ █ ▀█▀ █▀█ █▀█
|
|
||||||
# █░▀░█ █▄█ █░▀█ █ ░█░ █▄█ █▀▄
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Monitors/
|
|
||||||
|
|
||||||
# monitor = ,preferred,auto,auto
|
|
||||||
|
|
||||||
|
|
||||||
# █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█
|
|
||||||
# █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Keywords/
|
|
||||||
|
|
||||||
exec-once = $scrPath/resetxdgportal.sh # reset XDPH for screenshare
|
|
||||||
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH
|
|
||||||
exec-once = dbus-update-activation-environment --systemd --all # for XDPH
|
|
||||||
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH
|
|
||||||
exec-once = $scrPath/polkitkdeauth.sh # authentication dialogue for GUI apps
|
|
||||||
exec-once = waybar # launch the system bar
|
|
||||||
exec-once = blueman-applet # systray app for Bluetooth
|
|
||||||
exec-once = udiskie --no-automount --smart-tray # front-end that allows to manage removable media
|
|
||||||
exec-once = nm-applet --indicator # systray app for Network/Wifi
|
|
||||||
exec-once = dunst # start notification demon
|
|
||||||
exec-once = wl-paste --type text --watch cliphist store # clipboard store text data
|
|
||||||
exec-once = wl-paste --type image --watch cliphist store # clipboard store image data
|
|
||||||
exec-once = $scrPath/swwwallpaper.sh # start wallpaper daemon
|
|
||||||
exec-once = $scrPath/batterynotify.sh # battery notification
|
|
||||||
# (fl remaps keys with evremap (see .config/evremap/) — the old xremap exec-once was dead, no config here)
|
|
||||||
exec-once = swayidle -w timeout 180 'swaylock --timestr %H:%M & brightnessctl -s & brightnessctl set 15' resume 'brightnessctl -r'
|
|
||||||
|
|
||||||
# █▀▀ █▄░█ █░█
|
|
||||||
# ██▄ █░▀█ ▀▄▀
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Environment-variables/
|
|
||||||
|
|
||||||
env = PATH,$PATH:$scrPath
|
|
||||||
env = XDG_CURRENT_DESKTOP,Hyprland
|
|
||||||
env = XDG_SESSION_TYPE,wayland
|
|
||||||
env = XDG_SESSION_DESKTOP,Hyprland
|
|
||||||
env = QT_QPA_PLATFORM,wayland;xcb
|
|
||||||
env = QT_QPA_PLATFORMTHEME,qt6ct
|
|
||||||
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
|
|
||||||
env = QT_AUTO_SCREEN_SCALE_FACTOR,1
|
|
||||||
env = MOZ_ENABLE_WAYLAND,1
|
|
||||||
env = GDK_SCALE,1
|
|
||||||
|
|
||||||
|
|
||||||
# █ █▄░█ █▀█ █░█ ▀█▀
|
|
||||||
# █ █░▀█ █▀▀ █▄█ ░█░
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Variables/
|
|
||||||
|
|
||||||
# keyremap
|
|
||||||
input {
|
|
||||||
kb_layout = us
|
|
||||||
# kb_options=caps:swapescape
|
|
||||||
# kb_options=caps:ctrl_modifier
|
|
||||||
# follow_mouse = 1
|
|
||||||
|
|
||||||
touchpad {
|
|
||||||
natural_scroll = no
|
|
||||||
}
|
|
||||||
|
|
||||||
force_no_accel = 0
|
|
||||||
sensitivity = +1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Keywords/#executing
|
|
||||||
|
|
||||||
device {
|
|
||||||
name = ps/2-generic-mouse
|
|
||||||
sensitivity = +1.0
|
|
||||||
middle_button_emulation = true
|
|
||||||
}
|
|
||||||
|
|
||||||
device {
|
|
||||||
name = synps/2-synaptics-touchpad
|
|
||||||
sensitivity = 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
device {
|
|
||||||
name = xremap
|
|
||||||
sensitivity = +1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Variables/
|
|
||||||
|
|
||||||
# gestures {
|
|
||||||
# workspace_swipe = true
|
|
||||||
# workspace_swipe_fingers = 3
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
# █░░ ▄▀█ █▄█ █▀█ █░█ ▀█▀ █▀
|
|
||||||
# █▄▄ █▀█ ░█░ █▄█ █▄█ ░█░ ▄█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/
|
|
||||||
|
|
||||||
dwindle {
|
|
||||||
pseudotile = yes
|
|
||||||
preserve_split = yes
|
|
||||||
}
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Master-Layout/
|
|
||||||
|
|
||||||
master {
|
|
||||||
new_status = master
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# █▀▄▀█ █ █▀ █▀▀
|
|
||||||
# █░▀░█ █ ▄█ █▄▄
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Variables/
|
|
||||||
|
|
||||||
misc {
|
|
||||||
vrr = 0
|
|
||||||
disable_hyprland_logo = true
|
|
||||||
disable_splash_rendering = true
|
|
||||||
force_default_wallpaper = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
xwayland {
|
|
||||||
force_zero_scaling = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# █▀ █▀█ █░█ █▀█ █▀▀ █▀▀
|
|
||||||
# ▄█ █▄█ █▄█ █▀▄ █▄▄ ██▄
|
|
||||||
|
|
||||||
source = ~/.config/hypr/animations.conf
|
|
||||||
source = ~/.config/hypr/keybindings.conf
|
|
||||||
source = ~/.config/hypr/windowrules.conf
|
|
||||||
source = ~/.config/hypr/themes/common.conf # shared theme settings
|
|
||||||
source = ~/.config/hypr/themes/theme.conf # theme specific settings
|
|
||||||
source = ~/.config/hypr/themes/colors.conf # wallbash color override
|
|
||||||
source = ~/.config/hypr/monitors.conf # initially empty, to be configured by user and remains static
|
|
||||||
source = ~/.config/hypr/userprefs.conf # initially empty, to be configured by user and remains static
|
|
||||||
|
|
||||||
# Note: as userprefs.conf is sourced at the end, settings configured in this file will override the defaults
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
|
|
||||||
# █▄▀ █▀▀ █▄█ █▄▄ █ █▄░█ █▀▄ █ █▄░█ █▀▀ █▀
|
|
||||||
# █░█ ██▄ ░█░ █▄█ █ █░▀█ █▄▀ █ █░▀█ █▄█ ▄█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Keywords/
|
|
||||||
# & https://wiki.hyprland.org/Configuring/Binds/
|
|
||||||
|
|
||||||
# Main modifier
|
|
||||||
$mainMod = Super # super / meta / windows key
|
|
||||||
|
|
||||||
# Assign apps
|
|
||||||
$term = kitty # fish alacritty
|
|
||||||
$file = dolphin
|
|
||||||
$browser = librewolf
|
|
||||||
$yt = freetube
|
|
||||||
|
|
||||||
|
|
||||||
$wm=Window Management
|
|
||||||
$d=[$wm]
|
|
||||||
|
|
||||||
|
|
||||||
# Window/Session actions
|
|
||||||
bind = $mainMod, Q, exec, $scrPath/dontkillsteam.sh # close focused window
|
|
||||||
bind = Alt, F4, exec, $scrPath/dontkillsteam.sh # close focused window
|
|
||||||
bind = $mainMod, Delete, exit, # kill hyprland session
|
|
||||||
bind = $mainMod, W, togglefloating, # toggle the window between focus and float
|
|
||||||
bind = $mainMod, G, togglegroup, # toggle the window between focus and group
|
|
||||||
bind = Alt, Return, fullscreen, # toggle the window between focus and fullscreen
|
|
||||||
bind = $mainMod, L, exec, swaylock --timestr %H:%M # launch lock screen
|
|
||||||
bind = Ctrl+Alt, W, exec, killall waybar || waybar # toggle waybar
|
|
||||||
# bind = $mainMod+Shift, F, exec, $scrPath/windowpin.sh # toggle pin on focused window
|
|
||||||
# bind = $mainMod, Backspace, exec, $scrPath/logoutlaunch.sh # launch logout menu
|
|
||||||
|
|
||||||
# Application shortcuts
|
|
||||||
bind = $mainMod, T, exec, $term
|
|
||||||
bind = Ctrl+Alt, T, exec, $term # launch terminal emulator
|
|
||||||
bind = Ctrl+Shift+Alt, T, exec, st # launch tilix terminal emulator - using for ssh
|
|
||||||
bind = $mainMod+Shift, T, exec, st
|
|
||||||
bind = $mainMod+Ctrl, T, exec, tilix # launch tilix terminal emulator
|
|
||||||
bind = $mainMod, E, exec, $file # launch file manager
|
|
||||||
bind = $mainMod, F, exec, $yt # launch yt alternative
|
|
||||||
bind = $mainMod, B, exec, $browser # launch web browser
|
|
||||||
bind = $mainMod, G, exec, gajim # launch messanger
|
|
||||||
bind = $mainMod, K, exec, keepassxc # launch password manager
|
|
||||||
# bindd = $mainMod, N, Neovim, exec, $term -e nvim
|
|
||||||
bindd = $mainMod, R, $d pyradio, exec, kitty --session ~/.config/kitty/music.conf
|
|
||||||
bindd = Ctrl+Shift, Escape, Btop, exec, kitty -e btop
|
|
||||||
bindd = Ctrl+Alt, Delete, Btop, exec, kitty -e btop
|
|
||||||
# bind = Ctrl+Shift, Escape, exec, $scrPath/sysmonlaunch.sh # launch system monitor (htop/btop or fallback to top)
|
|
||||||
# bind = $mainMod+Shift, B, exec, $browser, title: ^(LibreWolf Private Browsing)$ # launch private web browser
|
|
||||||
|
|
||||||
|
|
||||||
# Rofi menus
|
|
||||||
bind = $mainMod, A, exec, pkill -x rofi || $scrPath/rofilaunch.sh d # launch application launcher
|
|
||||||
bind = $mainMod, Tab, exec, pkill -x rofi || $scrPath/rofilaunch.sh w # launch window switcher
|
|
||||||
bind = $mainMod+Shift, E, exec, pkill -x rofi || $scrPath/rofilaunch.sh f # launch file explorer
|
|
||||||
|
|
||||||
# Audio control
|
|
||||||
# bindl = , F10, exec, $scrPath/volumecontrol.sh -o m # toggle audio mute
|
|
||||||
# bindel = , F11, exec, $scrPath/volumecontrol.sh -o d # decrease volume
|
|
||||||
# bindel = , F12, exec, $scrPath/volumecontrol.sh -o i # increase volume
|
|
||||||
bindl = , XF86AudioMute, exec, $scrPath/volumecontrol.sh -o m # toggle audio mute
|
|
||||||
bindl = , XF86AudioMicMute, exec, $scrPath/volumecontrol.sh -i m # toggle microphone mute
|
|
||||||
bindel = , XF86AudioLowerVolume, exec, $scrPath/volumecontrol.sh -o d # decrease volume
|
|
||||||
bindel = , XF86AudioRaiseVolume, exec, $scrPath/volumecontrol.sh -o i # increase volume
|
|
||||||
|
|
||||||
# Media control
|
|
||||||
bindl = , XF86AudioPlay, exec, playerctl play-pause # toggle between media play and pause
|
|
||||||
bindl = , XF86AudioPause, exec, playerctl play-pause # toggle between media play and pause
|
|
||||||
bindl = , XF86AudioNext, exec, playerctl next # media next
|
|
||||||
bindl = , XF86AudioPrev, exec, playerctl previous # media previous
|
|
||||||
|
|
||||||
# Brightness control
|
|
||||||
bindel = , XF86MonBrightnessUp, exec, $scrPath/brightnesscontrol.sh i # increase brightness
|
|
||||||
bindel = , XF86MonBrightnessDown, exec, $scrPath/brightnesscontrol.sh d # decrease brightness
|
|
||||||
|
|
||||||
# Move between grouped windows
|
|
||||||
# bind = $mainMod CTRL , H, changegroupactive, b
|
|
||||||
# bind = $mainMod CTRL , L, changegroupactive, f
|
|
||||||
|
|
||||||
# Screenshot/Screencapture
|
|
||||||
bind = $mainMod, P, exec, $scrPath/screenshot.sh s # partial screenshot capture
|
|
||||||
bind = $mainMod+Ctrl, P, exec, $scrPath/screenshot.sh sf # partial screenshot capture (frozen screen)
|
|
||||||
bind = $mainMod+Alt, P, exec, $scrPath/screenshot.sh m # monitor screenshot capture
|
|
||||||
bind = $mainMod+Shift, P, exec, hyprpicker # colorpicker
|
|
||||||
bind = , Print, exec, $scrPath/screenshot.sh p # all monitors screenshot capture
|
|
||||||
|
|
||||||
# Custom scripts
|
|
||||||
bind = $mainMod+Alt, G, exec, $scrPath/gamemode.sh # disable hypr effects for gamemode
|
|
||||||
bind = $mainMod+Alt, Right, exec, $scrPath/swwwallpaper.sh -n # next wallpaper
|
|
||||||
bind = $mainMod+Alt, Left, exec, $scrPath/swwwallpaper.sh -p # previous wallpaper
|
|
||||||
# bind = $mainMod+Alt, Up, exec, $scrPath/wbarconfgen.sh n # next waybar mode
|
|
||||||
# bind = $mainMod+Alt, Down, exec, $scrPath/wbarconfgen.sh p # previous waybar mode
|
|
||||||
bind = $mainMod+Shift, R, exec, pkill -x rofi || $scrPath/wallbashtoggle.sh -m # launch wallbash mode select menu
|
|
||||||
# bind = $mainMod+Shift, T, exec, pkill -x rofi || $scrPath/themeselect.sh # launch theme select menu
|
|
||||||
bind = $mainMod+Shift, A, exec, pkill -x rofi || $scrPath/rofiselect.sh # launch select menu
|
|
||||||
bind = $mainMod+Shift, W, exec, pkill -x rofi || $scrPath/swwwallselect.sh # launch wallpaper select menu
|
|
||||||
bind = $mainMod, V, exec, pkill -x rofi || $scrPath/cliphist.sh c # launch clipboard
|
|
||||||
# bind = $mainMod, K, exec, $scrPath/keyboardswitch.sh # switch keyboard layout
|
|
||||||
bind = $mainMod, slash, exec, pkill -x rofi || $scrPath/keybinds_hint.sh c # launch keybinds hint
|
|
||||||
|
|
||||||
# Move/Change window focus
|
|
||||||
bind = $mainMod, Left, movefocus, l #chage focus to left window
|
|
||||||
bind = $mainMod, Right, movefocus, r
|
|
||||||
bind = $mainMod, Up, movefocus, u
|
|
||||||
bind = $mainMod, Down, movefocus, d
|
|
||||||
bindd = $mainMod Control, Left, $d focus left, movefocus, l
|
|
||||||
bindd = $mainMod Control, Right, $d focus right , movefocus, r
|
|
||||||
bindd = $mainMod Control, Up, $d focus up , movefocus, u
|
|
||||||
bindd = $mainMod Control, Down, $d focus down, movefocus, d
|
|
||||||
bindd = $mainMod Control, h, $d focus left, movefocus, l
|
|
||||||
bindd = $mainMod Control, l, $d focus right , movefocus, r
|
|
||||||
bindd = $mainMod Control, k, $d focus up , movefocus, u
|
|
||||||
bindd = $mainMod Control, j , $d focus down, movefocus, d
|
|
||||||
bindd = Alt Control, Left, $d focus left, movefocus, l
|
|
||||||
bindd = Alt Control, Right, $d focus right , movefocus, r
|
|
||||||
bindd = Alt Control, Up, $d focus up , movefocus, u
|
|
||||||
bindd = Alt Control, Down, $d focus down, movefocus, d
|
|
||||||
bindd = Alt Control, h, $d focus left, movefocus, l
|
|
||||||
bindd = Alt Control, l, $d focus right , movefocus, r
|
|
||||||
bindd = Alt Control, k, $d focus up , movefocus, u
|
|
||||||
bindd = Alt Control, j , $d focus down, movefocus, d
|
|
||||||
bindd = Alt, Tab, $d focus , movefocus, d
|
|
||||||
|
|
||||||
# Move active window around current workspace with mainMod + Shift + Control [←→↑↓]
|
|
||||||
$d=[$wm|Move active window across workspace]
|
|
||||||
$moveactivewindow=grep -q "true" <<< $(hyprctl activewindow -j | jq -r .floating) && hyprctl dispatch moveactive
|
|
||||||
bindde = $mainMod Alt, left, Move activewindow to the left, exec, $moveactivewindow -30 0 || hyprctl dispatch movewindow l
|
|
||||||
bindde = $mainMod Alt, right, Move activewindow to the right, exec, $moveactivewindow 30 0 || hyprctl dispatch movewindow r
|
|
||||||
bindde = $mainMod Alt, up, Move activewindow up, exec, $moveactivewindow 0 -30 || hyprctl dispatch movewindow u
|
|
||||||
bindde = $mainMod Alt, down, Move activewindow down, exec, $moveactivewindow 0 30 || hyprctl dispatch movewindow d
|
|
||||||
bindde = $mainMod Alt, h, Move activewindow to the left, exec, $moveactivewindow -30 0 || hyprctl dispatch movewindow l
|
|
||||||
bindde = $mainMod Alt, l, Move activewindow to the right, exec, $moveactivewindow 30 0 || hyprctl dispatch movewindow r
|
|
||||||
bindde = $mainMod Alt, k, Move activewindow up, exec, $moveactivewindow 0 -30 || hyprctl dispatch movewindow u
|
|
||||||
bindde = $mainMod Alt, j, Move activewindow down, exec, $moveactivewindow 0 30 || hyprctl dispatch movewindow d
|
|
||||||
|
|
||||||
# Switch workspaces with mod + [0-9]
|
|
||||||
bind = $mainMod, 1, workspace, r~1
|
|
||||||
bind = $mainMod, 2, workspace, r~2
|
|
||||||
bind = $mainMod, 3, workspace, r~3
|
|
||||||
bind = $mainMod, 4, workspace, r~4
|
|
||||||
bind = $mainMod, 5, workspace, r~5
|
|
||||||
bind = $mainMod, 6, workspace, r~6
|
|
||||||
bind = $mainMod, 7, workspace, r~7
|
|
||||||
bind = $mainMod, 8, workspace, r~8
|
|
||||||
bind = $mainMod, 9, workspace, r~9
|
|
||||||
bind = $mainMod, 0, workspace, r~10
|
|
||||||
|
|
||||||
# Move focused window to a workspace
|
|
||||||
bind = $mainMod+Ctrl, 1, movetoworkspace, r~1
|
|
||||||
bind = $mainMod+Ctrl, 2, movetoworkspace, r~2
|
|
||||||
bind = $mainMod+Ctrl, 3, movetoworkspace, r~3
|
|
||||||
bind = $mainMod+Ctrl, 4, movetoworkspace, r~4
|
|
||||||
bind = $mainMod+Ctrl, 5, movetoworkspace, r~5
|
|
||||||
bind = $mainMod+Ctrl, 6, movetoworkspace, r~6
|
|
||||||
bind = $mainMod+Ctrl, 7, movetoworkspace, r~7
|
|
||||||
bind = $mainMod+Ctrl, 8, movetoworkspace, r~8
|
|
||||||
bind = $mainMod+Ctrl, 9, movetoworkspace, r~9
|
|
||||||
bind = $mainMod+Ctrl, 0, movetoworkspace, r~10
|
|
||||||
|
|
||||||
# Move focused window to a workspace silently
|
|
||||||
bind = $mainMod+Alt, 1, movetoworkspacesilent, r~1
|
|
||||||
bind = $mainMod+Alt, 2, movetoworkspacesilent, r~2
|
|
||||||
bind = $mainMod+Alt, 3, movetoworkspacesilent, r~3
|
|
||||||
bind = $mainMod+Alt, 4, movetoworkspacesilent, r~4
|
|
||||||
bind = $mainMod+Alt, 5, movetoworkspacesilent, r~5
|
|
||||||
bind = $mainMod+Alt, 6, movetoworkspacesilent, r~6
|
|
||||||
bind = $mainMod+Alt, 7, movetoworkspacesilent, r~7
|
|
||||||
bind = $mainMod+Alt, 8, movetoworkspacesilent, r~8
|
|
||||||
bind = $mainMod+Alt, 9, movetoworkspacesilent, r~9
|
|
||||||
bind = $mainMod+Alt, 0, movetoworkspacesilent, r~10
|
|
||||||
|
|
||||||
# Move/Switch to special workspace (scratchpad)
|
|
||||||
bind = $mainMod+Alt, S, movetoworkspacesilent, special
|
|
||||||
bind = $mainMod, S, togglespecialworkspace,
|
|
||||||
|
|
||||||
# Toggle focused window split
|
|
||||||
bind = $mainMod, J, togglesplit
|
|
||||||
|
|
||||||
$d=[$ws|Navigation|Relative workspace]
|
|
||||||
bindd = $mainMod Control Shift, Right, $d change active workspace forwards , workspace, r+1
|
|
||||||
bindd = $mainMod Control Shift, Left, $d change active workspace backwards , workspace, r-1
|
|
||||||
bindd = $mainMod Control Shift, L, $d change active workspace forwards , workspace, r+1
|
|
||||||
bindd = $mainMod Control Shift, H, $d change active workspace backwards , workspace, r-1
|
|
||||||
|
|
||||||
# Resize windows
|
|
||||||
binde = $mainMod+Shift, Right, resizeactive, 30 0
|
|
||||||
binde = $mainMod+Shift, Left, resizeactive, -30 0
|
|
||||||
binde = $mainMod+Shift, Up, resizeactive, 0 -30
|
|
||||||
binde = $mainMod+Shift, Down, resizeactive, 0 30
|
|
||||||
bindde = $mainMod Shift, l, $d resize window right , resizeactive, 30 0
|
|
||||||
bindde = $mainMod Shift, h, $d resize window left, resizeactive, -30 0
|
|
||||||
bindde = $mainMod Shift, k, $d resize window up, resizeactive, 0 -30
|
|
||||||
bindde = $mainMod Shift, j, $d resize window down, resizeactive, 0 30
|
|
||||||
|
|
||||||
# added that to mimic i3's behavior, where pushing a workspace to another monitor also focuses it
|
|
||||||
# bind = $mainMod SHIFT, P, focusmonitor, +1
|
|
||||||
# bind = $mainMod SHIFT, P, movecurrentworkspacetomonitor, +1
|
|
||||||
|
|
||||||
# Move active window around current workspace with mainMod + SHIFT + CTRL [←→↑↓]
|
|
||||||
# $moveactivewindow=grep -q "true" <<< $(hyprctl activewindow -j | jq -r .floating) && hyprctl dispatch moveactive
|
|
||||||
binded = $mainMod+Shift+Ctrl, left,Move activewindow to the left,exec, $moveactivewindow -30 0 || hyprctl dispatch movewindow l
|
|
||||||
binded = $mainMod+Shift+Ctrl, right,Move activewindow to the right,exec, $moveactivewindow 30 0 || hyprctl dispatch movewindow r
|
|
||||||
binded = $mainMod+Shift+Ctrl, up,Move activewindow to the up,exec, $moveactivewindow 0 -30 || hyprctl dispatch movewindow u
|
|
||||||
binded = $mainMod+Shift+Ctrl, down,Move activewindow to the down,exec, $moveactivewindow 0 30 || hyprctl dispatch movewindow d
|
|
||||||
|
|
||||||
binded = $mainMod+Shift+Ctrl, h,Move activewindow to the left,exec, $moveactivewindow -30 0 || hyprctl dispatch movewindow l
|
|
||||||
binded = $mainMod+Shift+Ctrl, l,Move activewindow to the right,exec, $moveactivewindow 30 0 || hyprctl dispatch movewindow r
|
|
||||||
binded = $mainMod+Shift+Ctrl, k,Move activewindow to the up,exec, $moveactivewindow 0 -30 || hyprctl dispatch movewindow u
|
|
||||||
binded = $mainMod+Shift+Ctrl, j,Move activewindow to the down,exec, $moveactivewindow 0 30 || hyprctl dispatch movewindow d
|
|
||||||
|
|
||||||
# Scroll through existing workspaces
|
|
||||||
bind = $mainMod, mouse_down, workspace, e+1
|
|
||||||
bind = $mainMod, mouse_up, workspace, e-1
|
|
||||||
|
|
||||||
# Move/Resize focused window
|
|
||||||
bindm = $mainMod, mouse:272, movewindow
|
|
||||||
bindm = $mainMod, mouse:273, resizewindow
|
|
||||||
bindm = $mainMod, Z, movewindow
|
|
||||||
bindm = $mainMod, X, resizewindow
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
|
|
||||||
# █▀▄▀█ █▀█ █▄░█ █ ▀█▀ █▀█ █▀█ █▀
|
|
||||||
# █░▀░█ █▄█ █░▀█ █ ░█░ █▄█ █▀▄ ▄█
|
|
||||||
|
|
||||||
# Set your monitor configuration here
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Monitors/
|
|
||||||
# For a sample file, please refer to https://github.com/prasanthrangan/hyprdots/blob/main/Configs/.config/hypr/monitors.t2
|
|
||||||
|
|
||||||
$mainScreen = eDP-1
|
|
||||||
$leftScreen = DP-2
|
|
||||||
$rightScreen = HDMI-A-1
|
|
||||||
|
|
||||||
monitor = $mainScreen, 1366x768@60.14, 0x0, 1 # main screen
|
|
||||||
monitor = $leftScreen, 1920x1080@60, -1920x0, 1 # left
|
|
||||||
monitor = $rightScreen, 1920x1080@60,1366x0, 1 # right
|
|
||||||
|
|
||||||
# monitor=HDMI-A-1,1920x1080@60,-1366x0,1 # hdmi on left
|
|
||||||
# monitor=HDMI-A-1,1920x1080@60,0x-768,1 # hdmi on top
|
|
||||||
# monitor=DP-2,640x480@59.94Hz,0x0,1 # vga
|
|
||||||
|
|
||||||
# monitor = HDMI-A-1, 1366x768@60.00Hz, auto , 1, mirror, eDP-1 #mirror lapscreen
|
|
||||||
# monitor = eDP-1, 1920x1080@60.00Hz, auto , 1, mirror, HDMI-A-1 #main hdmi, lapscreen mirrors
|
|
||||||
|
|
||||||
# monitor = , preferred, auto, 1, mirror, DP-2 # dmz projector
|
|
||||||
# monitor = eDP-1, 1920x1080@60, auto , 1, mirror, DP-2 #mirror vga test
|
|
||||||
|
|
||||||
#Main Monitor
|
|
||||||
workspace = 1, monitor:$mainScreen, default:true
|
|
||||||
workspace = 2, monitor:$mainScreen
|
|
||||||
workspace = 3, monitor:$mainScreen
|
|
||||||
workspace = 4, monitor:$mainScreen
|
|
||||||
workspace = 5, monitor:$mainScreen
|
|
||||||
workspace = 6, monitor:$mainScreen
|
|
||||||
workspace = 7, monitor:$mainScreen
|
|
||||||
workspace = 8, monitor:$mainScreen
|
|
||||||
workspace = 9, monitor:$mainScreen
|
|
||||||
workspace = 10,monitor:$mainScreen
|
|
||||||
|
|
||||||
# Second Monitor
|
|
||||||
workspace = 11, monitor:$rightScreen, default:true
|
|
||||||
workspace = 12, monitor:$rightScreen
|
|
||||||
workspace = 13, monitor:$rightScreen
|
|
||||||
workspace = 14, monitor:$rightScreen
|
|
||||||
workspace = 15, monitor:$rightScreen
|
|
||||||
workspace = 16, monitor:$rightScreen
|
|
||||||
workspace = 17, monitor:$rightScreen
|
|
||||||
workspace = 18, monitor:$rightScreen
|
|
||||||
workspace = 19, monitor:$rightScreen
|
|
||||||
workspace = 20, monitor:$rightScreen
|
|
||||||
|
|
||||||
# Third Monitor
|
|
||||||
workspace = 21, monitor:$leftScreen, default:true
|
|
||||||
workspace = 22, monitor:$leftScreen
|
|
||||||
workspace = 23, monitor:$leftScreen
|
|
||||||
workspace = 24, monitor:$leftScreen
|
|
||||||
workspace = 25, monitor:$leftScreen
|
|
||||||
workspace = 26, monitor:$leftScreen
|
|
||||||
workspace = 27, monitor:$leftScreen
|
|
||||||
workspace = 28, monitor:$leftScreen
|
|
||||||
workspace = 29, monitor:$leftScreen
|
|
||||||
workspace = 30, monitor:$leftScreen
|
|
||||||
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
|
|
||||||
# █░█ █▀ █▀▀ █▀█ █▀█ █▀█ █▀▀ █▀▀ █▀
|
|
||||||
# █▄█ ▄█ ██▄ █▀▄ █▀▀ █▀▄ ██▄ █▀░ ▄█
|
|
||||||
|
|
||||||
# Set your personal hyprland configuration here
|
|
||||||
# For a sample file, please refer to https://github.com/prasanthrangan/hyprdots/blob/main/Configs/.config/hypr/userprefs.t2
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
########################
|
|
||||||
|
|
||||||
|
|
||||||
# █▀▀ █▄░█ █░█
|
|
||||||
# ██▄ █░▀█ ▀▄▀
|
|
||||||
|
|
||||||
# env = MANGOHUD,1
|
|
||||||
# env = MANGOHUD_DLSYM,1
|
|
||||||
|
|
||||||
|
|
||||||
# █▄▀ █▀▀ █▄█ █▄▄ █ █▄░█ █▀▄ █ █▄░█ █▀▀ █▀
|
|
||||||
# █░█ ██▄ ░█░ █▄█ █ █░▀█ █▄▀ █ █░▀█ █▄█ ▄█
|
|
||||||
|
|
||||||
# bind = Ctrl+Alt+Shift, R, pass, ^(com\.obsproject\.Studio)$ # toggle obs screen recording // install obs flatpak
|
|
||||||
# bind = $mainMod+Shift, G, exec, pkill -x rofi || $scrPath/gamelauncher.sh # launch steam game launcher // install steam
|
|
||||||
# bind = $mainMod, Period, exec, emote # launch emoji selector // install emote
|
|
||||||
bind = $mainMod+Alt, XF86MonBrightnessDown, exec, hyprshade on blue-light-filter # enable blue light filter // install hyprshade
|
|
||||||
bind = $mainMod+Alt, XF86MonBrightnessUp, exec, hyprshade off # disable blue light filter // install hyprshade
|
|
||||||
|
|
||||||
|
|
||||||
# █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█
|
|
||||||
# █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█
|
|
||||||
|
|
||||||
# exec-once = swayidle -w timeout 600 'swaylock' timeout 900 'hyprctl dispatch dpms off' resume 'hyprctl dispatch dpms on' # lock after 10 mins, sleep after 15 mins // install swayidle
|
|
||||||
# exec-once = swayidle -w timeout 1200 'swaylock; hyprctl dispatch dpms off' resume 'hyprctl dispatch dpms on' timeout 1800 'systemctl suspend' # lock and sleep after 20 mins, suspend after 30 mins // install swayidle
|
|
||||||
# exec-once = libinput-gestures // install libinput-gestures
|
|
||||||
# exec-once = swayosd-server # enable swayosd service
|
|
||||||
# exec-once = `pkexec swayosd-libinput-backend` # swayosd service for keyboard input (requires to be run in a subshell)
|
|
||||||
|
|
||||||
# █░█░█ █ █▄░█ █▀▄ █▀█ █░█░█ █▀█ █░█ █░░ █▀▀ █▀
|
|
||||||
# ▀▄▀▄▀ █ █░▀█ █▄▀ █▄█ ▀▄▀▄▀ █▀▄ █▄█ █▄▄ ██▄ ▄█
|
|
||||||
|
|
||||||
windowrulev2 = opacity 0.90 0.90,class:^(neovide)$
|
|
||||||
windowrulev2 = opacity 0.90 0.90,class:^(firefox)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(betterbird)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(org.qbittorrent.qBittorrent)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(org.gajim.Gajim)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(Chromium)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(FreeTube)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(LibreWolf)$
|
|
||||||
windowrulev2 = opacity 0.80 0.60,class:^(obsidian)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(VSCodium)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(codium-url-handler)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(code-oss)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(Code)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(kitty)$
|
|
||||||
windowrulev2 = opacity 0.70 0.70,class:^(tilix)$
|
|
||||||
windowrulev2 = opacity 0.70 0.70,class:^(rustdesk)$
|
|
||||||
windowrulev2 = opacity 0.70 0.70,class:^(Ferdium)$
|
|
||||||
windowrulev2 = opacity 0.70 0.70,class:^(org.keepassxc.KeePassXC)$
|
|
||||||
|
|
||||||
# █░█ █▀▄▀█
|
|
||||||
# ▀▄▀ █░▀░█
|
|
||||||
|
|
||||||
# bind = Ctrl+Alt_L, V, submap, passthrough
|
|
||||||
# submap = passthrough
|
|
||||||
# bind = Ctrl+Alt_L, V, submap, reset
|
|
||||||
# submap = reset
|
|
||||||
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
|
|
||||||
# █░█░█ █ █▄░█ █▀▄ █▀█ █░█░█ █▀█ █░█ █░░ █▀▀ █▀
|
|
||||||
# ▀▄▀▄▀ █ █░▀█ █▄▀ █▄█ ▀▄▀▄▀ █▀▄ █▄█ █▄▄ ██▄ ▄█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Window-Rules/
|
|
||||||
# hyprctl clients to get names
|
|
||||||
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(code-url-handler)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(code-insiders-url-handler)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(org.kde.dolphin)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(org.kde.ark)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(nwg-look)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(qt5ct)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(qt6ct)$
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(kvantummanager)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(org.pulseaudio.pavucontrol)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(lagrange)$
|
|
||||||
windowrulev2 = opacity 0.60 0.60,class:^(com.github.wwmm.easyeffects)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(blueman-manager)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(nm-applet)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(nm-connection-editor)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(org.kde.polkit-kde-authentication-agent-1)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(polkit-gnome-authentication-agent-1)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(org.freedesktop.impl.portal.desktop.gtk)$
|
|
||||||
windowrulev2 = opacity 0.80 0.70,class:^(org.freedesktop.impl.portal.desktop.hyprland)$
|
|
||||||
windowrulev2 = opacity 0.70 0.70,class:^([Ss]team)$
|
|
||||||
|
|
||||||
windowrulev2 = opacity 0.90 0.90,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(com.github.tchx84.Flatseal)$ # Flatseal-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(hu.kramo.Cartridges)$ # Cartridges-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(com.obsproject.Studio)$ # Obs-Qt
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(gnome-boxes)$ # Boxes-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(discord)$ # Discord-Electron
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(WebCord)$ # WebCord-Electron
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(ArmCord)$ # ArmCord-Electron
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(app.drey.Warp)$ # Warp-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(yad)$ # Protontricks-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(Signal)$ # Signal-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(io.github.alainm23.planify)$ # planify-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(io.gitlab.adhami3310.Impression)$ # Impression-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(io.missioncenter.MissionCenter)$ # MissionCenter-Gtk
|
|
||||||
windowrulev2 = opacity 0.80 0.80,class:^(io.github.flattool.Warehouse)$ # Warehouse-Gtk
|
|
||||||
|
|
||||||
windowrulev2 = float,class:^(org.kde.dolphin)$,title:^(Progress Dialog — Dolphin)$
|
|
||||||
windowrulev2 = float,class:^(org.kde.dolphin)$,title:^(Copying — Dolphin)$
|
|
||||||
windowrulev2 = float,title:^(About Mozilla Firefox)$
|
|
||||||
windowrulev2 = float,class:^(firefox)$,title:^(Picture-in-Picture)$
|
|
||||||
windowrulev2 = float,class:^(firefox)$,title:^(Library)$
|
|
||||||
windowrulev2 = float,class:^(kitty)$,title:^(top)$
|
|
||||||
windowrulev2 = float,class:^(kitty)$,title:^(btop)$
|
|
||||||
windowrulev2 = float,class:^(kitty)$,title:^(htop)$
|
|
||||||
windowrulev2 = float,class:^(vlc)$
|
|
||||||
windowrulev2 = float,class:^(kvantummanager)$
|
|
||||||
windowrulev2 = float,class:^(qt5ct)$
|
|
||||||
windowrulev2 = float,class:^(qt6ct)$
|
|
||||||
windowrulev2 = float,class:^(nwg-look)$
|
|
||||||
windowrulev2 = float,class:^(org.kde.ark)$
|
|
||||||
windowrulev2 = float,class:^(org.pulseaudio.pavucontrol)$
|
|
||||||
windowrulev2 = float,class:^(blueman-manager)$
|
|
||||||
windowrulev2 = float,class:^(nm-applet)$
|
|
||||||
windowrulev2 = float,class:^(nm-connection-editor)$
|
|
||||||
windowrulev2 = float,class:^(org.kde.polkit-kde-authentication-agent-1)$
|
|
||||||
|
|
||||||
windowrulev2 = float,class:^(Signal)$ # Signal-Gtk
|
|
||||||
windowrulev2 = float,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk
|
|
||||||
windowrulev2 = float,class:^(app.drey.Warp)$ # Warp-Gtk
|
|
||||||
windowrulev2 = float,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt
|
|
||||||
windowrulev2 = float,class:^(yad)$ # Protontricks-Gtk
|
|
||||||
windowrulev2 = float,class:^(eog)$ # Imageviewer-Gtk
|
|
||||||
windowrulev2 = float,class:^(io.github.alainm23.planify)$ # planify-Gtk
|
|
||||||
windowrulev2 = float,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk
|
|
||||||
windowrulev2 = float,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gkk
|
|
||||||
windowrulev2 = float,class:^(io.gitlab.adhami3310.Impression)$ # Impression-Gtk
|
|
||||||
windowrulev2 = float,class:^(io.missioncenter.MissionCenter)$ # MissionCenter-Gtk
|
|
||||||
|
|
||||||
|
|
||||||
# █░░ ▄▀█ █▄█ █▀▀ █▀█ █▀█ █░█ █░░ █▀▀ █▀
|
|
||||||
# █▄▄ █▀█ ░█░ ██▄ █▀▄ █▀▄ █▄█ █▄▄ ██▄ ▄█
|
|
||||||
|
|
||||||
layerrule = blur,rofi
|
|
||||||
layerrule = ignorezero,rofi
|
|
||||||
layerrule = blur,notifications
|
|
||||||
layerrule = ignorezero,notifications
|
|
||||||
layerrule = blur,swaync-notification-window
|
|
||||||
layerrule = ignorezero,swaync-notification-window
|
|
||||||
layerrule = blur,swaync-control-center
|
|
||||||
layerrule = ignorezero,swaync-control-center
|
|
||||||
layerrule = blur,logout_dialog
|
|
||||||
@@ -11,10 +11,6 @@ Switch to Last-Used Keyboard Layout=none,Meta+Alt+L,Switch to Last-Used Keyboard
|
|||||||
Switch to Next Keyboard Layout=none,Meta+Alt+K,Switch to Next Keyboard Layout
|
Switch to Next Keyboard Layout=none,Meta+Alt+K,Switch to Next Keyboard Layout
|
||||||
_k_friendly_name=Keyboard Layout Switcher
|
_k_friendly_name=Keyboard Layout Switcher
|
||||||
|
|
||||||
[kaccess]
|
|
||||||
Toggle Screen Reader On and Off=Meta+Alt+S,Meta+Alt+S,Toggle Screen Reader On and Off
|
|
||||||
_k_friendly_name=Accessibility
|
|
||||||
|
|
||||||
[kmix]
|
[kmix]
|
||||||
_k_friendly_name=KMix
|
_k_friendly_name=KMix
|
||||||
decrease_microphone_volume=Microphone Volume Down,Microphone Volume Down,Decrease Microphone Volume
|
decrease_microphone_volume=Microphone Volume Down,Microphone Volume Down,Decrease Microphone Volume
|
||||||
@@ -53,8 +49,8 @@ Increase Opacity=none,none,Increase Opacity of Active Window by 5%
|
|||||||
Kill Window=Meta+Ctrl+Esc,Meta+Ctrl+Esc,Kill Window
|
Kill Window=Meta+Ctrl+Esc,Meta+Ctrl+Esc,Kill Window
|
||||||
Move Tablet to Next LogicalOutput=none,none,Move the tablet to the next output
|
Move Tablet to Next LogicalOutput=none,none,Move the tablet to the next output
|
||||||
Move Tablet to Next Output=none,none,Move the tablet to the next output
|
Move Tablet to Next Output=none,none,Move the tablet to the next output
|
||||||
MoveMouseToCenter=Meta+F6,Meta+F6,Move Mouse to Center
|
MoveMouseToCenter=Meta+F6,Meta+F6,Center Pointer in Screen
|
||||||
MoveMouseToFocus=Meta+F5,Meta+F5,Move Mouse to Focus
|
MoveMouseToFocus=Meta+F5,Meta+F5,Center Pointer in Focused Window
|
||||||
MoveZoomDown=none,none,Move Zoomed Area Downwards
|
MoveZoomDown=none,none,Move Zoomed Area Downwards
|
||||||
MoveZoomLeft=none,none,Move Zoomed Area to Left
|
MoveZoomLeft=none,none,Move Zoomed Area to Left
|
||||||
MoveZoomRight=none,none,Move Zoomed Area to Right
|
MoveZoomRight=none,none,Move Zoomed Area to Right
|
||||||
@@ -62,15 +58,15 @@ MoveZoomUp=none,none,Move Zoomed Area Upwards
|
|||||||
Overview=none,Meta+W,Toggle Overview
|
Overview=none,Meta+W,Toggle Overview
|
||||||
Setup Window Shortcut=none,none,Setup Window Shortcut
|
Setup Window Shortcut=none,none,Setup Window Shortcut
|
||||||
Show Desktop=Meta+D,Meta+D,Peek at Desktop
|
Show Desktop=Meta+D,Meta+D,Peek at Desktop
|
||||||
Switch One Desktop Down=none,Meta+Ctrl+Down,Switch One Desktop Down
|
Switch One Desktop Down=Meta+Ctrl+Down,Meta+Ctrl+Down,Switch One Desktop Down
|
||||||
Switch One Desktop Up=Meta+Ctrl+Shift+Up,Meta+Ctrl+Up,Switch One Desktop Up
|
Switch One Desktop Up=Meta+Ctrl+Up\tMeta+Ctrl+Shift+Up,Meta+Ctrl+Up,Switch One Desktop Up
|
||||||
Switch One Desktop to the Left=none,Meta+Ctrl+Left,Switch One Desktop to the Left
|
Switch One Desktop to the Left=Meta+Ctrl+Left,Meta+Ctrl+Left,Switch One Desktop to the Left
|
||||||
Switch One Desktop to the Right=none,Meta+Ctrl+Right,Switch One Desktop to the Right
|
Switch One Desktop to the Right=Meta+Ctrl+Right,Meta+Ctrl+Right,Switch One Desktop to the Right
|
||||||
Switch Window Down=Meta+Shift+K\tMeta+Shift+Down\tMeta+Ctrl+Down\tMeta+Ctrl+J\tCtrl+Alt+Down\tCtrl+Alt+J,Meta+Alt+Down,Switch to Window Below
|
Switch Window Down=Ctrl+Alt+J\tCtrl+Alt+Down\tMeta+Shift+K\tMeta+Shift+Down\tMeta+Ctrl+J,Meta+Alt+Down,Switch to Window Below
|
||||||
Switch Window Left=Meta+Shift+H\tMeta+Shift+Left\tMeta+Ctrl+H\tCtrl+Alt+Left\tCtrl+Alt+H,Meta+Alt+Left,Switch to Window to the Left
|
Switch Window Left=Ctrl+Alt+H\tCtrl+Alt+Left\tMeta+Shift+H\tMeta+Shift+Left\tMeta+Ctrl+H,Meta+Alt+Left,Switch to Window to the Left
|
||||||
Switch Window Right=Meta+Shift+L\tMeta+Shift+Right\tMeta+Ctrl+L\tCtrl+Alt+Right\tCtrl+Alt+L,Meta+Alt+Right,Switch to Window to the Right
|
Switch Window Right=Ctrl+Alt+L\tCtrl+Alt+Right\tMeta+Shift+L\tMeta+Shift+Right\tMeta+Ctrl+L,Meta+Alt+Right,Switch to Window to the Right
|
||||||
Switch Window Up=Meta+Shift+J\tMeta+Shift+Up\tMeta+Ctrl+Up\tMeta+Ctrl+K\tCtrl+Alt+Up\tCtrl+Alt+K,Meta+Alt+Up,Switch to Window Above
|
Switch Window Up=Ctrl+Alt+K\tCtrl+Alt+Up\tMeta+Shift+J\tMeta+Shift+Up\tMeta+Ctrl+K,Meta+Alt+Up,Switch to Window Above
|
||||||
Switch to Desktop 1=Ctrl+F1\tMeta+1\tMeta+Ctrl+Left,Ctrl+F1\tMeta+F1,Switch to Desktop 1
|
Switch to Desktop 1=Ctrl+F1\tMeta+1,Ctrl+F1\tMeta+F1,Switch to Desktop 1
|
||||||
Switch to Desktop 10=Meta+0,none,Switch to Desktop 10
|
Switch to Desktop 10=Meta+0,none,Switch to Desktop 10
|
||||||
Switch to Desktop 11=none,none,Switch to Desktop 11
|
Switch to Desktop 11=none,none,Switch to Desktop 11
|
||||||
Switch to Desktop 12=none,none,Switch to Desktop 12
|
Switch to Desktop 12=none,none,Switch to Desktop 12
|
||||||
@@ -81,7 +77,7 @@ Switch to Desktop 16=none,none,Switch to Desktop 16
|
|||||||
Switch to Desktop 17=none,none,Switch to Desktop 17
|
Switch to Desktop 17=none,none,Switch to Desktop 17
|
||||||
Switch to Desktop 18=none,none,Switch to Desktop 18
|
Switch to Desktop 18=none,none,Switch to Desktop 18
|
||||||
Switch to Desktop 19=none,none,Switch to Desktop 19
|
Switch to Desktop 19=none,none,Switch to Desktop 19
|
||||||
Switch to Desktop 2=Ctrl+F2\tMeta+2\tMeta+Ctrl+Right,Ctrl+F2\tMeta+F2,Switch to Desktop 2
|
Switch to Desktop 2=Ctrl+F2\tMeta+2,Ctrl+F2\tMeta+F2,Switch to Desktop 2
|
||||||
Switch to Desktop 20=none,none,Switch to Desktop 20
|
Switch to Desktop 20=none,none,Switch to Desktop 20
|
||||||
Switch to Desktop 21=none,none,Switch to Desktop 21
|
Switch to Desktop 21=none,none,Switch to Desktop 21
|
||||||
Switch to Desktop 22=none,none,Switch to Desktop 22
|
Switch to Desktop 22=none,none,Switch to Desktop 22
|
||||||
@@ -95,9 +91,9 @@ Switch to Desktop 6=Meta+6,none,Switch to Desktop 6
|
|||||||
Switch to Desktop 7=Meta+7,none,Switch to Desktop 7
|
Switch to Desktop 7=Meta+7,none,Switch to Desktop 7
|
||||||
Switch to Desktop 8=Meta+8,none,Switch to Desktop 8
|
Switch to Desktop 8=Meta+8,none,Switch to Desktop 8
|
||||||
Switch to Desktop 9=Meta+9,none,Switch to Desktop 9
|
Switch to Desktop 9=Meta+9,none,Switch to Desktop 9
|
||||||
Switch to Next Desktop=Meta+Ctrl+Shift+Right\tMeta+Ctrl+Shift+L,none,Switch to Next Desktop
|
Switch to Next Desktop=Meta+Ctrl+Shift+L\tMeta+Ctrl+Shift+Right,none,Switch to Next Desktop
|
||||||
Switch to Next Screen=none,none,Switch to Next Screen
|
Switch to Next Screen=none,none,Switch to Next Screen
|
||||||
Switch to Previous Desktop=Meta+Ctrl+Shift+Left\tMeta+Ctrl+Shift+H,none,Switch to Previous Desktop
|
Switch to Previous Desktop=Meta+Ctrl+Shift+H\tMeta+Ctrl+Shift+Left,none,Switch to Previous Desktop
|
||||||
Switch to Previous Screen=none,none,Switch to Previous Screen
|
Switch to Previous Screen=none,none,Switch to Previous Screen
|
||||||
Switch to Screen 0=none,none,Switch to Screen 0
|
Switch to Screen 0=none,none,Switch to Screen 0
|
||||||
Switch to Screen 1=none,none,Switch to Screen 1
|
Switch to Screen 1=none,none,Switch to Screen 1
|
||||||
@@ -112,10 +108,11 @@ Switch to Screen Below=none,none,Switch to Screen Below
|
|||||||
Switch to Screen to the Left=none,none,Switch to Screen to the Left
|
Switch to Screen to the Left=none,none,Switch to Screen to the Left
|
||||||
Switch to Screen to the Right=none,none,Switch to Screen to the Right
|
Switch to Screen to the Right=none,none,Switch to Screen to the Right
|
||||||
Toggle Night Color=none,none,Suspend/Resume Night Light
|
Toggle Night Color=none,none,Suspend/Resume Night Light
|
||||||
|
Toggle Show Compositing=none,none,Toggle “Show Compositing” effect
|
||||||
Toggle Window Raise/Lower=none,none,Toggle Window Raise/Lower
|
Toggle Window Raise/Lower=none,none,Toggle Window Raise/Lower
|
||||||
ToggleMouseClick=Meta+*,Meta+*,Toggle Mouse Click Animation
|
ToggleMouseClick=Meta+*,Meta+*,Toggle Mouse Click Animation
|
||||||
Walk Through Windows=Alt+Tab,Alt+Tab\tMeta+Tab,Walk Through Windows
|
Walk Through Windows=Alt+Tab,Alt+Tab\tMeta+Tab,Walk Through Windows
|
||||||
Walk Through Windows (Reverse)=Alt+Shift+Backtab,Alt+Shift+Tab\tMeta+Shift+Tab,Walk Through Windows (Reverse)
|
Walk Through Windows (Reverse)=Alt+Shift+Tab,Alt+Shift+Tab\tMeta+Shift+Tab,Walk Through Windows (Reverse)
|
||||||
Walk Through Windows Alternative=none,none,Walk Through Windows Alternative
|
Walk Through Windows Alternative=none,none,Walk Through Windows Alternative
|
||||||
Walk Through Windows Alternative (Reverse)=none,none,Walk Through Windows Alternative (Reverse)
|
Walk Through Windows Alternative (Reverse)=none,none,Walk Through Windows Alternative (Reverse)
|
||||||
Walk Through Windows of Current Application=Alt+`,Alt+`\tMeta+`,Walk Through Windows of Current Application
|
Walk Through Windows of Current Application=Alt+`,Alt+`\tMeta+`,Walk Through Windows of Current Application
|
||||||
@@ -129,7 +126,7 @@ Window Custom Quick Tile Bottom=none,none,Custom Quick Tile Window to the Bottom
|
|||||||
Window Custom Quick Tile Left=none,none,Custom Quick Tile Window to the Left
|
Window Custom Quick Tile Left=none,none,Custom Quick Tile Window to the Left
|
||||||
Window Custom Quick Tile Right=none,none,Custom Quick Tile Window to the Right
|
Window Custom Quick Tile Right=none,none,Custom Quick Tile Window to the Right
|
||||||
Window Custom Quick Tile Top=none,none,Custom Quick Tile Window to the Top
|
Window Custom Quick Tile Top=none,none,Custom Quick Tile Window to the Top
|
||||||
Window Fullscreen=Alt+Return\tShift+F11,none,Make Window Fullscreen
|
Window Fullscreen=Shift+F11\tAlt+Return,none,Make Window Fullscreen
|
||||||
Window Grow Horizontal=none,none,Expand Window Horizontally
|
Window Grow Horizontal=none,none,Expand Window Horizontally
|
||||||
Window Grow Vertical=none,none,Expand Window Vertically
|
Window Grow Vertical=none,none,Expand Window Vertically
|
||||||
Window Lower=none,none,Lower Window
|
Window Lower=none,none,Lower Window
|
||||||
@@ -192,9 +189,9 @@ Window to Desktop 6=Meta+Ctrl+6,none,Window to Desktop 6
|
|||||||
Window to Desktop 7=Meta+Ctrl+7,none,Window to Desktop 7
|
Window to Desktop 7=Meta+Ctrl+7,none,Window to Desktop 7
|
||||||
Window to Desktop 8=Meta+Ctrl+8,none,Window to Desktop 8
|
Window to Desktop 8=Meta+Ctrl+8,none,Window to Desktop 8
|
||||||
Window to Desktop 9=Meta+Ctrl+9,none,Window to Desktop 9
|
Window to Desktop 9=Meta+Ctrl+9,none,Window to Desktop 9
|
||||||
Window to Next Desktop=Meta+Ctrl+Alt+Right\tMeta+Ctrl+Alt+L,none,Window to Next Desktop
|
Window to Next Desktop=Meta+Ctrl+Alt+L\tMeta+Ctrl+Alt+Right,none,Window to Next Desktop
|
||||||
Window to Next Screen=none,Meta+Shift+Right,Move Window to Next Screen
|
Window to Next Screen=none,Meta+Shift+Right,Move Window to Next Screen
|
||||||
Window to Previous Desktop=Meta+Ctrl+Alt+Left\tMeta+Ctrl+Alt+H,none,Window to Previous Desktop
|
Window to Previous Desktop=Meta+Ctrl+Alt+H\tMeta+Ctrl+Alt+Left,none,Window to Previous Desktop
|
||||||
Window to Previous Screen=none,Meta+Shift+Left,Move Window to Previous Screen
|
Window to Previous Screen=none,Meta+Shift+Left,Move Window to Previous Screen
|
||||||
Window to Screen 0=none,none,Move Window to Screen 0
|
Window to Screen 0=none,none,Move Window to Screen 0
|
||||||
Window to Screen 1=none,none,Move Window to Screen 1
|
Window to Screen 1=none,none,Move Window to Screen 1
|
||||||
@@ -210,6 +207,36 @@ view_actual_size=Meta+),Meta+0,Zoom to Actual Size
|
|||||||
view_zoom_in=Meta++,Meta++\tMeta+=,Zoom In
|
view_zoom_in=Meta++,Meta++\tMeta+=,Zoom In
|
||||||
view_zoom_out=Meta+_,Meta+-,Zoom Out
|
view_zoom_out=Meta+_,Meta+-,Zoom Out
|
||||||
|
|
||||||
|
[kwin][$InverseAction]
|
||||||
|
Cycle Overview=Cycle Overview Opposite
|
||||||
|
Increase Opacity=Decrease Opacity
|
||||||
|
MoveZoomDown=MoveZoomUp
|
||||||
|
MoveZoomLeft=MoveZoomRight
|
||||||
|
Switch One Desktop Down=Switch One Desktop Up
|
||||||
|
Switch One Desktop to the Left=Switch One Desktop to the Right
|
||||||
|
Switch Window Right=Switch Window Left
|
||||||
|
Switch Window Up=Switch Window Down
|
||||||
|
Switch to Next Screen=Switch to Previous Screen
|
||||||
|
Switch to Previous Desktop=Switch to Next Desktop
|
||||||
|
Switch to Screen Below=Switch to Screen Above
|
||||||
|
Switch to Screen to the Right=Switch to Screen to the Left
|
||||||
|
Walk Through Windows (Reverse)=Walk Through Windows
|
||||||
|
Walk Through Windows Alternative=Walk Through Windows Alternative (Reverse)
|
||||||
|
Walk Through Windows of Current Application (Reverse)=Walk Through Windows of Current Application
|
||||||
|
Walk Through Windows of Current Application Alternative (Reverse)=Walk Through Windows of Current Application Alternative
|
||||||
|
Window Grow Horizontal=Window Shrink Horizontal
|
||||||
|
Window Grow Vertical=Window Shrink Vertical
|
||||||
|
Window One Desktop Up=Window One Desktop Down
|
||||||
|
Window One Desktop to the Left=Window One Desktop to the Right
|
||||||
|
Window One Screen Up=Window One Screen Down
|
||||||
|
Window One Screen to the Right=Window One Screen to the Left
|
||||||
|
Window Pack Right=Window Pack Left
|
||||||
|
Window Pack Up=Window Pack Down
|
||||||
|
Window Raise=Window Lower
|
||||||
|
Window to Next Screen=Window to Previous Screen
|
||||||
|
Window to Previous Desktop=Window to Next Desktop
|
||||||
|
view_zoom_in=view_zoom_out
|
||||||
|
|
||||||
[mediacontrol]
|
[mediacontrol]
|
||||||
_k_friendly_name=Media Controller
|
_k_friendly_name=Media Controller
|
||||||
mediavolumedown=none,none,Media volume down
|
mediavolumedown=none,none,Media volume down
|
||||||
@@ -245,6 +272,8 @@ powerProfile=Battery,Battery\tMeta+B,Switch Power Profile
|
|||||||
Slideshow Wallpaper Next Image=Meta+Ctrl+Alt+Shift+Right,none,Next Wallpaper Image
|
Slideshow Wallpaper Next Image=Meta+Ctrl+Alt+Shift+Right,none,Next Wallpaper Image
|
||||||
_k_friendly_name=plasmashell
|
_k_friendly_name=plasmashell
|
||||||
activate application launcher=Meta,Meta\tAlt+F1,Activate Application Launcher
|
activate application launcher=Meta,Meta\tAlt+F1,Activate Application Launcher
|
||||||
|
activate next task manager entry=Meta+Ctrl+PgDown,Meta+Ctrl+PgDown,Activate Next Task Manager Entry
|
||||||
|
activate previous task manager entry=Meta+Ctrl+PgUp,Meta+Ctrl+PgUp,Activate Previous Task Manager Entry
|
||||||
activate task manager entry 1=none,Meta+1,Activate Task Manager Entry 1
|
activate task manager entry 1=none,Meta+1,Activate Task Manager Entry 1
|
||||||
activate task manager entry 10=none,none,Activate Task Manager Entry 10
|
activate task manager entry 10=none,none,Activate Task Manager Entry 10
|
||||||
activate task manager entry 2=none,Meta+2,Activate Task Manager Entry 2
|
activate task manager entry 2=none,Meta+2,Activate Task Manager Entry 2
|
||||||
@@ -261,12 +290,14 @@ activate widget 69=none,none,Activate System Monitor Widget
|
|||||||
activate widget 70=none,none,Activate System Monitor Widget
|
activate widget 70=none,none,Activate System Monitor Widget
|
||||||
clear history=none,none,Clear Notification History
|
clear history=none,none,Clear Notification History
|
||||||
clear-history=none,none,Clear Clipboard History
|
clear-history=none,none,Clear Clipboard History
|
||||||
clipboard_action=Meta+Ctrl+X,Meta+Ctrl+X,Automatic Action Popup Menu
|
clipboard_action=none,none,Toggle Automatic Popping Up of Action Menu
|
||||||
cycle-panels=Meta+Alt+P,Meta+Alt+P,Move keyboard focus between panels
|
cycle-panels=Meta+Alt+P,Meta+Alt+P,Move keyboard focus between panels
|
||||||
cycleNextAction=none,none,Next History Item
|
cycleNextAction=none,none,Next History Item
|
||||||
cyclePrevAction=none,none,Previous History Item
|
cyclePrevAction=none,none,Previous History Item
|
||||||
edit_clipboard=none,none,Edit Contents…
|
edit_clipboard=none,none,Edit Contents…
|
||||||
manage activities=none,Meta+Q,Show Activity Switcher
|
manage activities=none,Meta+Q,Show Activity Switcher
|
||||||
|
move active task to left=Meta+Ctrl+Shift+PgUp,Meta+Ctrl+Shift+PgUp,Move Active Task Manager Entry Backward
|
||||||
|
move active task to right=Meta+Ctrl+Shift+PgDown,Meta+Ctrl+Shift+PgDown,Move Active Task Manager Entry Forward
|
||||||
next activity=none,none,Walk through activities
|
next activity=none,none,Walk through activities
|
||||||
previous activity=Meta+Shift+A,none,Walk through activities (Reverse)
|
previous activity=Meta+Shift+A,none,Walk through activities (Reverse)
|
||||||
repeat_action=Meta+Ctrl+R,none,Manually Invoke Action on Current Clipboard
|
repeat_action=Meta+Ctrl+R,none,Manually Invoke Action on Current Clipboard
|
||||||
@@ -335,7 +366,7 @@ _launch=Meta+K
|
|||||||
_launch=Meta+Ctrl+T
|
_launch=Meta+Ctrl+T
|
||||||
|
|
||||||
[services][systemsettings.desktop]
|
[services][systemsettings.desktop]
|
||||||
_launch=\tTools
|
_launch=Tools
|
||||||
|
|
||||||
[services][thorium-browser.desktop]
|
[services][thorium-browser.desktop]
|
||||||
_launch=Meta+Ctrl+B
|
_launch=Meta+Ctrl+B
|
||||||
@@ -70,9 +70,9 @@ starves the desktop and freezes the PC (reboot). Hard-won rules:
|
|||||||
| `GLM-4.7-Flash-UD-Q4_K_XL` | **21.5** @moe22 | 24k | reasoning | quality coder (opencode subagents); KV fix + moe22 ✓ verified clean 08-08 |
|
| `GLM-4.7-Flash-UD-Q4_K_XL` | **21.5** @moe22 | 24k | reasoning | quality coder (opencode subagents); KV fix + moe22 ✓ verified clean 08-08 |
|
||||||
| `GLM-4.7-Flash-UD-Q3_K_XL` | **26.0** @moe12 (2.0 daytime baseline; ~28 clean) | 24k | reasoning | **fast coder** — re-added 09-21 because Q4's 21.5 dragged in agent loops; 3-bit, evening-safe at moe12 (13.4 used / 3.8 free @2.0); Q4 stays for hard reviews |
|
| `GLM-4.7-Flash-UD-Q3_K_XL` | **26.0** @moe12 (2.0 daytime baseline; ~28 clean) | 24k | reasoning | **fast coder** — re-added 09-21 because Q4's 21.5 dragged in agent loops; 3-bit, evening-safe at moe12 (13.4 used / 3.8 free @2.0); Q4 stays for hard reviews |
|
||||||
| `Qwen3-Coder-Next-UD-IQ3_XXS` | 16.0 | **128k** | 80B-A3B | long-session coder (128k ctx) |
|
| `Qwen3-Coder-Next-UD-IQ3_XXS` | 16.0 | **128k** | 80B-A3B | long-session coder (128k ctx) |
|
||||||
| `Qwen3.8-27B-UD-IQ3_XXS` | **15.5** (10.9 w/o MTP) | 24k | vision, MTP, hybrid reasoner @ **xhigh** | ⚠ **clean-desktop specialty** (kept 09-10 over the ≥22 gate) — dense-27B quality, slow: its Gated-DeltaNet layers are Vulkan-bound (prefill ~35). 2.5 GB free @1.7 baseline @24k (✓ 10-02 on b11146, `-n 512`, acceptance 0.84). The *felt* slowness is the **xhigh thinking preamble** (17.5 s before the first answer token on a trivial prompt; `reasoning_effort=low` measured 6.45 s at the same decode on 10-02 — siblings benched then removed by decision, the kwarg is one line if ever wanted); **never on a busy evening desktop** (~1 GB free = GTT freeze) |
|
| `Qwen3.8-27B-UD-IQ3_XXS` | **36–37** on b11429 (10-22 ×2; was 15.5 on b11146, 10.9 base) | 24k | vision, MTP, hybrid reasoner @ **xhigh** | ⚠ **clean-desktop specialty** (kept 09-10 over the ≥22 gate — **the speed half is now passed 1.7× over**, since llama.cpp 0.6.0 halved the per-step MTP cost; role re-rank pending the user) — dense-27B quality; prefill still GDN-bound (~20–35). 2.7 GB free @1.3 baseline @24k (✓ 10-22 on b11429, `-n 512`, acceptance 0.89, len 2.78). The *felt* slowness is the **xhigh thinking preamble** (17.5 s before the first answer token on a trivial prompt; `reasoning_effort=low` measured 6.45 s at the same decode on 10-02 — siblings benched then removed by decision, the kwarg is one line if ever wanted); **never on a busy evening desktop** (~1 GB free = GTT freeze) |
|
||||||
| `Qwen3-Embedding-0.6B` | 33 (CPU) | 8k | CPU-only, `/v1/embeddings` | RAG/search embedder (not a chat model) |
|
| `Qwen3-Embedding-0.6B` | 33 (CPU) | 8k | CPU-only, `/v1/embeddings` | RAG/search embedder (not a chat model) |
|
||||||
| `Ornith-1.5-35B-A3B-IQ3_XXS` | **32.1** @moe16 | 24k | MTP, vision, thinking | **agentic coder** (added 09-21) — Qwen3.5-MoE derivative claiming +15 Terminal-Bench / +5.6 SWE-bench over Qwen3.6-35B; the only coder with MTP; 13.8 used / 3.3 free @2.0 |
|
| `Ornith-1.5-35B-A3B-IQ3_XXS` | **32.1** @moe16 | **32k** (10-22, unbenched) | MTP, vision, thinking | **agentic coder** (added 09-21) — Qwen3.5-MoE derivative claiming +15 Terminal-Bench / +5.6 SWE-bench over Qwen3.6-35B; the only coder with MTP; 13.8 used / 3.3 free @2.0 |
|
||||||
|
|
||||||
Expected run-to-run spread: MTP models swing ±15% with draft **acceptance rate** (content-
|
Expected run-to-run spread: MTP models swing ±15% with draft **acceptance rate** (content-
|
||||||
dependent); CPU-heavy presets (Coder-Next, embedder) dip under daytime CPU contention.
|
dependent); CPU-heavy presets (Coder-Next, embedder) dip under daytime CPU contention.
|
||||||
@@ -94,9 +94,9 @@ Treat clean night runs as the reference; don't retune on daytime deltas.
|
|||||||
- **Hard reasoning**: `35B-Thinking` (quality) or `gpt-oss-20b` (speed + tool use).
|
- **Hard reasoning**: `35B-Thinking` (quality) or `gpt-oss-20b` (speed + tool use).
|
||||||
Their multi-second TTFT is the reasoning phase streaming first — not a slow load.
|
Their multi-second TTFT is the reasoning phase streaming first — not a slow load.
|
||||||
For quick interactive gpt-oss answers use `gpt-oss-20b-low` (reasoning effort low).
|
For quick interactive gpt-oss answers use `gpt-oss-20b-low` (reasoning effort low).
|
||||||
- **Best single-model quality, speed no object** (quiet desktop only): `Qwen3.8-27B` — dense
|
- **Best single-model quality** (quiet desktop only): `Qwen3.8-27B` — dense 27B hybrid reasoner +
|
||||||
27B hybrid reasoner + vision at ~14.5 t/s; it cannot go faster on this card (GDN layers on
|
vision at **37 t/s since llama.cpp 0.6.0** (was ~15 on 0.5.0; prefill still GDN-bound on Vulkan),
|
||||||
Vulkan), and a busy evening desktop pushes it into GTT-freeze territory — check `rocm-smi` first.
|
but a busy evening desktop pushes it into GTT-freeze territory — check `rocm-smi` first.
|
||||||
- **Images**: `gemma-4-26B` for quality, `gemma-4-E4B` or `Qwen3.5-9B` for speed,
|
- **Images**: `gemma-4-26B` for quality, `gemma-4-E4B` or `Qwen3.5-9B` for speed,
|
||||||
`Qwen3.6-35B` when you want the daily driver to see the screenshot.
|
`Qwen3.6-35B` when you want the daily driver to see the screenshot.
|
||||||
- **Long one-shot documents**: `gemma-4-E4B` — 185 t/s prefill eats 64k in ~6 min
|
- **Long one-shot documents**: `gemma-4-E4B` — 185 t/s prefill eats 64k in ~6 min
|
||||||
@@ -150,8 +150,13 @@ the router, but chats/presets saved against the old id need re-picking.
|
|||||||
2026-09-10: kept as ⚠ clean-desktop specialty after the gate bench finally ran on the v3 gguf
|
2026-09-10: kept as ⚠ clean-desktop specialty after the gate bench finally ran on the v3 gguf
|
||||||
(14.5 t/s MTP / 10.9 base, 2.8 GB free @1.5); server ctx restored to 24576 = client ctx, no client edits.
|
(14.5 t/s MTP / 10.9 base, 2.8 GB free @1.5); server ctx restored to 24576 = client ctx, no client edits.
|
||||||
2026-09-21: `GLM-4.7-Flash-UD-Q3_K_XL` (⚡ fast coder) added to all six client files next to the Q4.
|
2026-09-21: `GLM-4.7-Flash-UD-Q3_K_XL` (⚡ fast coder) added to all six client files next to the Q4.
|
||||||
|
2026-10-22: Qwen3.8-27B quant race opened (ISTA GSQ-RCO IQ3_XXS-mtp + DFlash2 sibling) — bench-only,
|
||||||
|
NO client edits until a candidate wins; see the watch list. Same night: fl found on llama.cpp 0.6.0
|
||||||
|
(b11429) and the Qwen3.8 incumbent re-measured 37.1 t/s (15.5 on b11146) — every MTP/hybrid preset
|
||||||
|
on the roster needs a re-sweep on this build; ledger numbers above it are from 0.5.0 unless dated 10-22.
|
||||||
2026-09-21: `Ornith-1.5-35B-A3B-IQ3_XXS` (agentic coder, thinking + vision) added to all six client files after passing its gate (32.1 t/s @moe16).
|
2026-09-21: `Ornith-1.5-35B-A3B-IQ3_XXS` (agentic coder, thinking + vision) added to all six client files after passing its gate (32.1 t/s @moe16).
|
||||||
2026-09-21: default model → `Qwen3.6-35B-A3B-Thinking` everywhere (opencode global + plan/build/auto, pi defaultModel + thinking level medium, aichat common/lw/wm) — gemma-4-26B refused routine sysadmin work. Thinking preset gets `reasoning-budget 8192`. wm aichat caught up (GLM Q3, Ornith).
|
2026-09-21: default model → `Qwen3.6-35B-A3B-Thinking` everywhere (opencode global + plan/build/auto, pi defaultModel + thinking level medium, aichat common/lw/wm) — gemma-4-26B refused routine sysadmin work. Thinking preset gets `reasoning-budget 8192`. wm aichat caught up (GLM Q3, Ornith).
|
||||||
|
2026-10-22: Ornith in pi: `Compacting in OVERFLOW recovery` right after a truncated reply is pi's *truncated-reply* recovery, not a full window — pi clamps each request's `max_tokens` to `contextWindow − estimate − 4096`, which on 24k/4096 starts exactly at the compaction threshold, so the first request after a compaction (estimate gains the system prompt + tool schemas) can leave a thinking reply ~1024 answer tokens → `length` → compact + retry. `compaction-watch.ts` now names the case with numbers; `common/.pi/CLAUDE.md` > Compaction tuning has the arithmetic. Applied the same day, UNBENCHED: Ornith `ctx-size` 24576 → 32768 + the seven client files (pi/opencode/aichat, common+lw+wm; predicted ≈ 0.1 GB of KV on this hybrid arch) — restart llama.service, then `./bench.py -m Ornith-1.5-35B-A3B-IQ3_XXS`; gate and the revert path are in the preset comment.
|
||||||
2026-10-12: Ornith in pi: "Response was truncated before completion." = `finish_reason: length` — the `<think>` block counts against pi's 4096 `max_tokens` and the preset has no reasoning-budget. Fixed client-side: pi sends `thinking_budget_tokens` per thinking level on Ornith (`compat.thinkingTokenBudgetField` + `settings.json > thinkingBudgets`, common + lw). The preset deliberately keeps NO CLI reasoning-budget — it would override the per-request field (see the preset comment).
|
2026-10-12: Ornith in pi: "Response was truncated before completion." = `finish_reason: length` — the `<think>` block counts against pi's 4096 `max_tokens` and the preset has no reasoning-budget. Fixed client-side: pi sends `thinking_budget_tokens` per thinking level on Ornith (`compat.thinkingTokenBudgetField` + `settings.json > thinkingBudgets`, common + lw). The preset deliberately keeps NO CLI reasoning-budget — it would override the per-request field (see the preset comment).
|
||||||
|
|
||||||
## Tuning cheat-sheet
|
## Tuning cheat-sheet
|
||||||
@@ -200,9 +205,27 @@ reasoning-effort siblings; for stable MTP decode numbers use `-n 512`.
|
|||||||
## Watch list
|
## Watch list
|
||||||
|
|
||||||
- **GLM MTP in llama.cpp** — the single biggest pending win (primary coder ~22 → ~30+).
|
- **GLM MTP in llama.cpp** — the single biggest pending win (primary coder ~22 → ~30+).
|
||||||
- **DFlash2 for Qwen3.8-27B** — `z-lab/Qwen3.8-27B-DFlash2-GGUF` drafter, upstream 1.81× vs the
|
- **Qwen3.8-27B quant race (opened 2026-10-22, bench-only, not client-wired)** — two candidate
|
||||||
MTP head's 1.33× here (b11146 has the support). Blockers: #27408 (spec + mmproj → image requests
|
sections in `config.ini` after the incumbent: `Qwen3.8-27B-GSQ-RCO-IQ3_XXS` (ISTA-DASLab GSQ-RCO
|
||||||
500, so only as a text-only sibling preset) and #28158 (Vulkan OOB draft token, open). Trial =
|
file, 10.44 GB with MTP head = −0.49 GB vs unsloth v3) and its `-dflash` text-only sibling
|
||||||
`spec-type draft-dflash`, `spec-draft-n-max 3`, no mmproj; n-max 7 does not fit the VRAM envelope.
|
(`z-lab/Qwen3.8-27B-DFlash2-GGUF` Q4_K_M drafter, `spec-draft-n-max 3`). Expected: +4 % decode and
|
||||||
|
+0.5 GB free from the file; +5–15 % over MTP from DFlash2 on RDNA3/Vulkan (not the paper's 1.8×).
|
||||||
|
Quality is unmeasured against our file — the user's own prompts decide. First run 10-22 03:14: both
|
||||||
|
candidates HTTP 400 = fl's config.ini not pulled yet (router: "model not found"); the incumbent in
|
||||||
|
the same run read **37.1 t/s on b11429** (see its row). Re-run 03:25, one session: incumbent 36.2 /
|
||||||
|
2.7 free, **GSQ 37.5 / 3.2 free** (+3.6 %, +0.5 GB — speed+VRAM passed, **quality call open**),
|
||||||
|
**DFlash2 15.4 / 1.9 free → dropped** (tombstoned in `config.ini`). Ruled out by the same
|
||||||
|
research: ByteShape IQ4_XS (13.1 GB), ISTA IQ3_S (12.1 GB with MTP, under the floor), the
|
||||||
|
adaptive KV-cache-streaming fork (CUDA-only, PCIe 5.0). Still open: #27408 (spec + mmproj → 500),
|
||||||
|
#28158 (Vulkan OOB draft token). Research + verdicts + download lines: the config block.
|
||||||
|
- **llama.cpp 0.7.0 (b11669) — HOLD fl at 0.6.0**: #30688, Vulkan draft-mtp decode 4× / prefill 6×
|
||||||
|
slower (target compute buffer lands in GTT = this box's freeze precursor). Every MTP preset on the
|
||||||
|
roster is exposed. When `pacman -Si llama-cpp` on fl shows 0.7.0, `IgnorePkg` it until fixed.
|
||||||
|
The 0.5.0 → 0.6.0 jump itself (Qwen3.8 15.5 → 37) is llama-side, best explained by #29856 (hybrid
|
||||||
|
graph reserve; the old 10.9 base was probably GTT-starved, not kernel-bound — see the preset block).
|
||||||
|
**ggml 0.26.0** (next `-Syu`): #30039 reports a −45 % prefill regression on a GDN hybrid — bench
|
||||||
|
Qwen3.8 before and after; downgrade ggml/ggml-vulkan from the pacman cache if prefill tanks.
|
||||||
- **Coder-Next 256k**: if the load log's `n_ctx_train` says 262144, ctx can likely double
|
- **Coder-Next 256k**: if the load log's `n_ctx_train` says 262144, ctx can likely double
|
||||||
again for ~1 GB (KV measured nearly flat).
|
again for ~1 GB (KV measured nearly flat).
|
||||||
|
|
||||||
|
> **vLLM on this box** (bench rig, docker/ROCm, never resident together with the router): see `../vllm/README.md`.
|
||||||
@@ -102,7 +102,9 @@ def force_unload(base, key):
|
|||||||
print(f" (router /models not readable: {e} — relying on sleep-idle drain)", file=sys.stderr)
|
print(f" (router /models not readable: {e} — relying on sleep-idle drain)", file=sys.stderr)
|
||||||
return
|
return
|
||||||
for m in d.get("data", []):
|
for m in d.get("data", []):
|
||||||
if m.get("status", "loaded") != "loaded": # builds without 'status': try them all
|
st = m.get("status", "loaded") # b11146 sends {'value': 'loaded'|'sleeping'|'unloaded'}; older a string
|
||||||
|
st = st.get("value", "loaded") if isinstance(st, dict) else st
|
||||||
|
if st not in ("loaded", "loading"): # builds without 'status': try them all
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
_open(root + "/models/unload", key, {"model": m["id"]}, timeout=30).read()
|
_open(root + "/models/unload", key, {"model": m["id"]}, timeout=30).read()
|
||||||
|
|||||||
@@ -379,6 +379,36 @@ sleep-idle-seconds = 60
|
|||||||
# stall/500) → any trial is a separate TEXT-ONLY preset; this one keeps MTP + vision.
|
# stall/500) → any trial is a separate TEXT-ONLY preset; this one keeps MTP + vision.
|
||||||
# Open on Vulkan for MTP and DFlash alike: #28158 — intermittent draft token id 248320
|
# Open on Vulkan for MTP and DFlash alike: #28158 — intermittent draft token id 248320
|
||||||
# (= n_vocab) → HTTP 500 "Invalid input batch", mostly after very long prompts. If seen: that.
|
# (= n_vocab) → HTTP 500 "Invalid input batch", mostly after very long prompts. If seen: that.
|
||||||
|
# ✦ 2026-10-22 QUANT RACE OPENED (reddit tip: ByteShape / ISTA 3-bit quants + DFlash2 + KV streaming):
|
||||||
|
# two bench-only candidate sections follow this preset — [Qwen3.8-27B-GSQ-RCO-IQ3_XXS] (ISTA file,
|
||||||
|
# −0.49 GB, MTP) and its -dflash text-only sibling; the research + verdicts live in that block.
|
||||||
|
# ⚠ llama.cpp 0.7.0 (b11669) = #30688, Vulkan draft decode 4× slower (compute buffer lands in GTT):
|
||||||
|
# hold fl at 0.6.0 until fixed — hits EVERY MTP preset here, not only this one.
|
||||||
|
# ✦ BUILD DATUM 2026-10-22 03:15 — fl turned out to be on 0.6.0 (b11429) + ggml-vulkan 0.25.3-2 (the
|
||||||
|
# SAME kernels as 10-02): bench 37.1 t/s / 21 pp / TTFT 3.3 / 14.4 used / 2.7 free @1.3 (-n 512).
|
||||||
|
# Server: 221 tok @ 37.07, draft acceptance 0.892 (141/158), mean len 2.78 → ~75 ms per
|
||||||
|
# speculative step vs ~173 on b11146 — a llama.cpp-side change (b11147..b11429), NOT the Vulkan
|
||||||
|
# kernels (ggml is a separate package, unchanged). A 4096-token think-run: 27.3 t/s @ acceptance
|
||||||
|
# 0.51 / len 2.02 = the same ~74 ms step; acceptance sags over long thinking. Footprint 13.1,
|
||||||
|
# unchanged. → the ≥22 gate is PASSED 1.7× over; the 'clean-desktop specialty' verdict's SPEED half
|
||||||
|
# is obsolete (the VRAM half — never on a busy desktop — still stands). Role re-rank = user's call.
|
||||||
|
# 03:25 re-run: 36.2 / 45 pp / TTFT 8.6 / 14.5 / 2.7 — reproducible.
|
||||||
|
# CAUSE (researched 10-22; a hypothesis — no PR in b11147..b11429 claims any such gain): Arch's llama-cpp
|
||||||
|
# links the SYSTEM ggml (-DLLAMA_USE_SYSTEM_GGML=ON), so every ggml-side Vulkan PR (#29476 GDN tune,
|
||||||
|
# #29280 descriptor reuse, FA work) is EXCLUDED — those ship in ggml 0.26.0, not on fl yet. Best fit:
|
||||||
|
# #29856 (10-03) "gather the recurrent states once so the reserve covers every split" — on 0.5.0 the
|
||||||
|
# hybrid graph was silently re-reserved after the first speculative step and the target compute
|
||||||
|
# buffer could land partly in GTT = the #30688 mechanism seen from the other side. The arithmetic
|
||||||
|
# agrees: the old no-MTP base (10.9 t/s = 92 ms/token) was SLOWER than today's whole MTP step (75 ms
|
||||||
|
# for ≥3 verified tokens + 2 draft passes), so the "10.9 base = the GDN kernel wall" story above is
|
||||||
|
# SUSPECT: that base was most likely GTT-starved, not kernel-bound. Cheap check whenever the card is
|
||||||
|
# free: a TEMP -nomtp alias on 0.6.0 — ~22–26 t/s confirms it, ~11 refutes it. #27694 (probabilistic
|
||||||
|
# drafter, 10-02) explains acceptance 0.84 → 0.89, not the step time. (Also learned: the 9B is hybrid
|
||||||
|
# too, 24 GDN + 8 attn per #30039 — the "regular-attention qwen35" remark further down is wrong.)
|
||||||
|
# ⚠ ggml 0.26.0 (the next -Syu brings ggml + ggml-vulkan 0.26.0-1, SOVERSION-compatible, unrebuilt):
|
||||||
|
# #30039 reports −45 % PREFILL on a GDN hybrid with b11429 binaries, TG flat → bench this preset right
|
||||||
|
# before and after that upgrade; if prefill tanks, downgrade ggml/ggml-vulkan from the pacman cache.
|
||||||
|
# REFERENCE from here: 37.1 (b11429). Every other MTP/hybrid preset needs a re-sweep on this build.
|
||||||
# ⚠ Needs a CURRENT llama.cpp build: first "qwen35" file HERE with ssm.*/hybrid-attn keys
|
# ⚠ Needs a CURRENT llama.cpp build: first "qwen35" file HERE with ssm.*/hybrid-attn keys
|
||||||
# (the 9B is regular-attention qwen35). If the load fails with unknown-architecture /
|
# (the 9B is regular-attention qwen35). If the load fails with unknown-architecture /
|
||||||
# missing-tensor errors, update llama.cpp on fl before touching this preset.
|
# missing-tensor errors, update llama.cpp on fl before touching this preset.
|
||||||
@@ -418,6 +448,150 @@ min-p = 0
|
|||||||
jinja = on
|
jinja = on
|
||||||
sleep-idle-seconds = 120
|
sleep-idle-seconds = 120
|
||||||
|
|
||||||
|
# ═══ QUANT RACE 2026-10-22 — Qwen3.8-27B third-party 3-bit quants + DFlash2 (bench-only, NOT client-wired) ═══
|
||||||
|
# Trigger: r/LocalLLaMA 2026-09-24 "Qwen-3.8-27B is good enough that I stopped using API": a 16 GB user
|
||||||
|
# (RTX 5060 Ti, CUDA, PCIe 5.0 x8, display on the iGPU) runs ByteShape IQ4_XS + DFlash2 + Raymond's
|
||||||
|
# KV-cache streaming; a reply says "try the ISTA IQ3_S / XXS — more speed and context, couldn't tell a
|
||||||
|
# difference in quality". What survives contact with THIS box (16 GB + display, Vulkan/RADV, PCIe 4.0 x8):
|
||||||
|
# · KV-cache streaming (troed → RaymondHuang210129/llama.cpp-adaptive-kv-streaming): OUT. CUDA-only —
|
||||||
|
# the README lists Vulkan as "memory-infrastructure tests only" (no attention path), ROCm = one
|
||||||
|
# unreviewed PR for gfx1201; both authors say PCIe 5.0 is required (PCIe 4.0 users: 2–4 t/s @192k);
|
||||||
|
# no upstream PR (discussion #28216 only). And it is a 160k+ tool — clients run this model at 24k.
|
||||||
|
# · ByteShape (byteshape/Qwen3.8-27B-GGUF, proprietary "ShapeLearn" per-tensor mixed types, the IQ*
|
||||||
|
# labels are size classes; MTP head kept, quantized IQ3/IQ4 vs Q6_K/Q8_0 elsewhere): IQ4_XS-3.84bpw
|
||||||
|
# 13.08 GB → OUT (ceiling here ≈ 11 GB file); the commenter's own file is troed's 12.25 GB
|
||||||
|
# ASCII-vocab-pruned build (248320 → 129006 tokens) → OUT twice. Same size class as the incumbent:
|
||||||
|
# IQ3_S-3.23bpw 11.035 GB (vendor blog: 98.7 % of BF16 task avg vs 93.6 % for unsloth UD-IQ3_XXS —
|
||||||
|
# vendor numbers; the one independent KLD run ranks their IQ4_XS BELOW unsloth's UD-IQ4_XS, 0.050 vs
|
||||||
|
# 0.019), IQ3_XS-3.01bpw 10.353 GB, IQ3_XXS-2.88bpw 9.919 GB. Optional third runner, see below.
|
||||||
|
# · ISTA (ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF: GSQ = learned per-coordinate grids, RCO = per-tensor
|
||||||
|
# type under a size budget, standard GGUF block types, imatrix ~90 % English — discussion #7 reports
|
||||||
|
# degraded Chinese; "-mtp" files carry the head, +0.35 GB, weights identical):
|
||||||
|
# IQ3_S-mtp 12.12 GB → footprint ≈ 14.2 → ~1.4 free @1.6 = UNDER THE 1.5 FLOOR. Plain IQ3_S 11.77
|
||||||
|
# fits only WITHOUT any drafter (~3.4 free) at ~10 t/s → no. Both reddit comments measured "more
|
||||||
|
# speed and context" against a 13 GB file, not against a 3-bit one.
|
||||||
|
# IQ3_XXS-mtp 10.443 GB = 0.49 GB under the incumbent → the one ISTA file with a case here.
|
||||||
|
# Quality evidence (nobody measured it against OUR unsloth v3 file): ISTA card wiki-ppl 7.20 vs
|
||||||
|
# BF16 7.05, AIME25/GPQA-D/LCBv6 within 1–2 pts of BF16; ByteShape's blog scores it 94.4 vs 93.6
|
||||||
|
# for UD-IQ3_XXS (= equal); Agention's KLD table puts it at 0.11, ~2× their own IQ3_XXS. Thread:
|
||||||
|
# one user "ISTA even at lower quants did better", troed "tried it, not as good for my use".
|
||||||
|
# → bench.py settles speed + VRAM only; the quality half is the user's own prompts, both files.
|
||||||
|
# · DFlash2 (z-lab/Qwen3.8-27B-DFlash2-GGUF: 1.92B block-diffusion drafter, 5 layers, 2048-token
|
||||||
|
# sliding window, no own embeddings/output head; Q4_K_M 1.143 GB; llama.cpp support since b10658 =
|
||||||
|
# already in fl's b11146): acceptance length ~5.4 vs the MTP head's 2.6 here. RDNA3/Vulkan reports
|
||||||
|
# (PR #27342 thread): 7900 XTX RADV n-max 3 = +4–12 % decode over MTP, prefill −1 to −12 %; Strix
|
||||||
|
# Halo RADV n-max 4 = 19–23 t/s vs MTP 20; R9700 n-max 4 = 52 vs 47; a net LOSS once layers spill.
|
||||||
|
# Expect +5–15 % over the incumbent (37.1 on b11429, 10-22), NOT the paper's 1.8× — every verified draft token
|
||||||
|
# re-runs the 48 GDN layers. Acceptance falls with context (5090 datum: 31–36 % at 23–63k vs MTP
|
||||||
|
# 55 %) = why the redditor says "at low context".
|
||||||
|
# VRAM: drafter 1.14 GB + (1 + n-max) × ~150 MiB recurrent-state snapshots (n-max 3 ≈ 0.6 GB) +
|
||||||
|
# drafter KV — the MTP head measured 1.5 GB against a ~0.7 estimate, so budget ~2 GB and let the
|
||||||
|
# bench.py guard decide; the ISTA file's −0.49 GB is what buys the margin. Default n-max 7 = OUT.
|
||||||
|
# Text-only: #27408 (spec + mmproj → image requests 500) still open → NO mmproj on that preset.
|
||||||
|
# #28158 (Vulkan: draft token id 248320 → HTTP 500, rare, very long prompts) still open.
|
||||||
|
# Build window: #29226 (b11132) broke drafters WITH embeddings but WITHOUT an output head (fixed by
|
||||||
|
# #30111, b11512); the z-lab drafter has neither → should not be hit. FIRST GATE regardless:
|
||||||
|
# "draft acceptance" in the server log > 0 (0.00 = that class of breakage → drop the sibling).
|
||||||
|
# ⚠ BUILD: fl = llama-cpp 0.6.0-1 (b11429) + ggml 0.25.3-2 + ggml-vulkan 0.25.3-2, CONFIRMED 10-22 (the
|
||||||
|
# b11146 → b11429 move alone took the incumbent 15.5 → 37.1, see its block). Arch extra also has
|
||||||
|
# ggml/ggml-vulkan 0.26.0 (#27952 int8 coopmat prefill, #29476 GDN tune) — the next -Syu brings them
|
||||||
|
# = another variable, bench before/after. Upstream 0.7.0 (b11669) = #30688: Vulkan
|
||||||
|
# draft-mtp decode 4× / prefill 6× SLOWER, the target compute buffer re-allocated into GTT — on this
|
||||||
|
# box that is the freeze precursor, and it hits every MTP preset on the roster. When
|
||||||
|
# `pacman -Si llama-cpp` on fl shows 0.7.0: HOLD (IgnorePkg = llama-cpp ggml ggml-vulkan) until fixed.
|
||||||
|
# Race on the current build first (one variable per run); the ggml 0.26.0 bump is a separate round.
|
||||||
|
# PLAN (clean baseline, -n 512, incumbent re-run in the SAME session — the 10-02 band was 12.5–15.5):
|
||||||
|
# 1. download (below) · 2. `sudo systemctl restart llama.service` ·
|
||||||
|
# 3. ./bench.py -n 512 -m Qwen3.8-27B-UD-IQ3_XXS,Qwen3.8-27B-GSQ-RCO-IQ3_XXS,Qwen3.8-27B-GSQ-RCO-IQ3_XXS-dflash
|
||||||
|
# 4. quality: the same 3–4 real prompts (incl. non-English) against incumbent vs GSQ.
|
||||||
|
# GATE: GSQ-mtp ≥ incumbent decode (expected ~+4 %) with free ≥ 2.5 → quality is the user's call →
|
||||||
|
# if it wins, either point the incumbent section's `model =` at the GSQ file (keeps the client id,
|
||||||
|
# accepted misnomer) or rename = the seven-client-file round. DFlash sibling: acceptance > 0 AND
|
||||||
|
# decode ≥ 1.10 × the SAME-SESSION incumbent AND free ≥ 2.5 → keep as the text-only fast sibling
|
||||||
|
# (wire into clients then); else drop. Loser files: delete from ~/software/models (10.4 + 1.1 GB).
|
||||||
|
# ✦ RACE RUN 2026-10-22 03:25 @1.3 baseline, -n 512, all three in ONE session (rows in bench-history.md):
|
||||||
|
# incumbent UD-IQ3_XXS ....... 36.2 t/s / 45 pp / TTFT 8.6 / 14.5 used / 2.7 free
|
||||||
|
# GSQ-RCO-IQ3_XXS (MTP) ...... 37.5 / 44 / 7.0 / 14.0 / 3.2 → +3.6 % decode, +0.5 GB free = exactly the
|
||||||
|
# file-size prediction. Speed + VRAM half of the gate PASSED. OPEN: the quality half (same 3–4 real
|
||||||
|
# prompts, one non-English, against both; nobody has measured ISTA vs unsloth v3 at this size).
|
||||||
|
# GSQ -dflash ................ 15.4 / 32 / 18.0 / 15.3 / 1.9 → −59 % vs its own MTP row and under the
|
||||||
|
# 2.5 target → DROPPED (tombstone below). Read the acceptance line once: `journalctl -u llama.service
|
||||||
|
# --since 03:25 | grep 'draft acceptance'` — 0.00 = the #29226-window bug (fix #30111 = b11512 sits in
|
||||||
|
# 0.7.0 behind #30688 → re-probe only on a fixed 0.7.x); > 0 = block-drafting re-runs the 48 GDN
|
||||||
|
# layers per verified block and loses on this card (the RX 9070 XT datum in #25117 said the same)
|
||||||
|
# → closed for good. Either way nothing to tune: n-max 2 only shrinks the loss.
|
||||||
|
# NEXT: quality call → if GSQ wins, point the incumbent's `model =` at the GSQ file (keeps the client
|
||||||
|
# id; the section name becomes a misnomer) or do the seven-client rename round, then delete the
|
||||||
|
# unsloth file; if not, delete the GSQ file and the section. The DFlash2 drafter can go now.
|
||||||
|
# ⬇ DOWNLOAD on fl — curl straight into the models dir (the HF cache has root-owned dirs from docker):
|
||||||
|
# cd ~/software/models
|
||||||
|
# curl -L -o Qwen3.8-27B-GSQ-RCO-IQ3_XXS-mtp.gguf https://huggingface.co/ISTA-DASLab/Qwen3.8-27B-GSQ-RCO-GGUF/resolve/main/Qwen3.8-27B-GSQ-RCO-IQ3_XXS-mtp.gguf
|
||||||
|
# (dropped 10-22, only for a re-probe) curl -L -o Qwen3.8-27B-DFlash2-Q4_K_M.gguf https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2-GGUF/resolve/main/Qwen3.8-27B-DFlash2-Q4_K_M.gguf
|
||||||
|
# stat -c '%s %n' Qwen3.8-27B-GSQ-RCO-IQ3_XXS-mtp.gguf Qwen3.8-27B-DFlash2-Q4_K_M.gguf # 10442827840 · 1143006816
|
||||||
|
# (mmproj: ISTA ships the same BF16 encoder — the existing mmproj-Qwen3.8-27B-F16.gguf is reused)
|
||||||
|
|
||||||
|
[Qwen3.8-27B-GSQ-RCO-IQ3_XXS]
|
||||||
|
# ⚗ RACE CANDIDATE 2026-10-22 — bench-only, NOT in any client file. = the incumbent preset with the ISTA
|
||||||
|
# file swapped in (MTP head Q6_K, same as unsloth's). Predicted @1.6 baseline @24k: ~14.1 used /
|
||||||
|
# ~3.0 free (incumbent 14.4 / 2.7), decode ~38–39 (incumbent 37.1 on b11429) — bandwidth × file size, nothing
|
||||||
|
# else moves. Knobs deliberately identical to the incumbent (n-max 2) for a clean A/B.
|
||||||
|
# ✓ MEASURED 2026-10-22 03:25 @1.3: 37.5 t/s / 44 pp / TTFT 7.0 / 14.0 used / 3.2 free (incumbent 36.2 /
|
||||||
|
# 2.7 in the same run). Speed + VRAM passed; QUALITY CALL PENDING — not client-wired until then.
|
||||||
|
model = /home/anon/software/models/Qwen3.8-27B-GSQ-RCO-IQ3_XXS-mtp.gguf
|
||||||
|
mmproj = /home/anon/software/models/mmproj-Qwen3.8-27B-F16.gguf
|
||||||
|
spec-type = draft-mtp
|
||||||
|
spec-draft-n-max = 2
|
||||||
|
ctx-size = 24576
|
||||||
|
n-gpu-layers = 99
|
||||||
|
threads = 6
|
||||||
|
flash-attn = on
|
||||||
|
cache-type-k = q8_0
|
||||||
|
cache-type-v = q4_0
|
||||||
|
batch-size = 2048
|
||||||
|
ubatch-size = 256
|
||||||
|
cache-reuse = 256
|
||||||
|
defrag-thold = 0.1
|
||||||
|
no-mmproj-offload = true
|
||||||
|
temp = 1.0
|
||||||
|
top-p = 0.95
|
||||||
|
top-k = 20
|
||||||
|
min-p = 0
|
||||||
|
jinja = on
|
||||||
|
sleep-idle-seconds = 120
|
||||||
|
|
||||||
|
# ─── DROPPED 2026-10-22 03:25: [Qwen3.8-27B-GSQ-RCO-IQ3_XXS-dflash] (DFlash2 text-only sibling) ─────
|
||||||
|
# 15.4 t/s / 32 pp / 15.3 used / 1.9 free vs 37.5 / 3.2 for the same file with its MTP head — the
|
||||||
|
# z-lab block drafter is a net loss on this card (see the RACE RUN entry above for the two possible
|
||||||
|
# causes and the only re-probe condition: a 0.7.x build that carries #30111 without #30688).
|
||||||
|
# Removed as a loadable section on purpose: OWUI lists every router id, and 1.9 free on a busy
|
||||||
|
# desktop is the GTT freeze. File can be deleted from the models dir: Qwen3.8-27B-DFlash2-Q4_K_M.gguf
|
||||||
|
# Keys as benched (n-max 3, no mmproj — #27408):
|
||||||
|
# model = /home/anon/software/models/Qwen3.8-27B-GSQ-RCO-IQ3_XXS-mtp.gguf
|
||||||
|
# model-draft = /home/anon/software/models/Qwen3.8-27B-DFlash2-Q4_K_M.gguf
|
||||||
|
# spec-type = draft-dflash
|
||||||
|
# spec-draft-n-max = 3
|
||||||
|
# ctx-size = 24576
|
||||||
|
# n-gpu-layers = 99
|
||||||
|
# threads = 6
|
||||||
|
# flash-attn = on
|
||||||
|
# cache-type-k = q8_0
|
||||||
|
# cache-type-v = q4_0
|
||||||
|
# batch-size = 2048
|
||||||
|
# ubatch-size = 256
|
||||||
|
# cache-reuse = 256
|
||||||
|
# defrag-thold = 0.1
|
||||||
|
# temp = 1.0
|
||||||
|
# top-p = 0.95
|
||||||
|
# top-k = 20
|
||||||
|
# min-p = 0
|
||||||
|
# jinja = on
|
||||||
|
# sleep-idle-seconds = 120
|
||||||
|
|
||||||
|
# ─── optional 3rd runner (not added — copy the GSQ section if wanted): ByteShape IQ3_S-3.23bpw ────
|
||||||
|
# 11.035 GB = the incumbent's size, vendor-claimed best quality in the class, MTP head kept (IQ3-quantized).
|
||||||
|
# model = /home/anon/software/models/Qwen3.8-27B-IQ3_S-3.23bpw.gguf
|
||||||
|
# curl -L -o Qwen3.8-27B-IQ3_S-3.23bpw.gguf https://huggingface.co/byteshape/Qwen3.8-27B-GGUF/resolve/main/Qwen3.8-27B-IQ3_S-3.23bpw.gguf # 11035354496 B
|
||||||
|
|
||||||
# ─── RETIRED 2026-07-16: [Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M] ──────────────
|
# ─── RETIRED 2026-07-16: [Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M] ──────────────
|
||||||
# Dense: 10 t/s, 2.0 GB free (under the 2.5 target) and unfixable — bandwidth-bound, smaller
|
# Dense: 10 t/s, 2.0 GB free (under the 2.5 target) and unfixable — bandwidth-bound, smaller
|
||||||
# quant would cost the quality that justified it. Removed from opencode/pi/aichat.
|
# quant would cost the quality that justified it. Removed from opencode/pi/aichat.
|
||||||
@@ -552,6 +726,22 @@ sleep-idle-seconds = 30
|
|||||||
# here "like the Thinking preset" silently disables the pi knob and brings back pi's
|
# here "like the Thinking preset" silently disables the pi knob and brings back pi's
|
||||||
# "Response was truncated before completion." (= finish_reason length: think block + answer
|
# "Response was truncated before completion." (= finish_reason length: think block + answer
|
||||||
# > the client's 4096 max_tokens). An instruct alias (budget 0) in a SEPARATE section is fine.
|
# > the client's 4096 max_tokens). An instruct alias (budget 0) in a SEPARATE section is fine.
|
||||||
|
# ⚠ 2026-10-22: pi's "Compacting in OVERFLOW recovery" on this preset is NOT this window filling
|
||||||
|
# up — pi clamps each request's max_tokens to contextWindow − estimate − 4096, which on
|
||||||
|
# 24k/4096 starts exactly at its compaction threshold; the first request after a compaction
|
||||||
|
# (estimate gains the system prompt + tool schemas) can leave the thinking reply ~1024 answer
|
||||||
|
# tokens → finish_reason length → pi compacts again and retries. Arithmetic + options in
|
||||||
|
# common/.pi/CLAUDE.md > Compaction tuning > max_tokens clamp.
|
||||||
|
# APPLIED 2026-10-22 (same day, unbenched): ctx-size 24576 → 32768 here + 32768 in the seven
|
||||||
|
# client files (pi contextWindow both blocks common+lw, opencode limit.context common+lw,
|
||||||
|
# aichat max_input_tokens 30000 common+lw+wm). Threshold and clamp in pi now sit at 24576,
|
||||||
|
# above anything a fresh post-compaction context reaches. Predicted cost ≈ 0.1 GB: hybrid
|
||||||
|
# GDN 3:1 = 10 attention layers with small GQA, ~8 KB/token of q8/q4 KV → ~70 MB for +8192,
|
||||||
|
# plus a few MB of FA scratch — read the "KV self size" line in the load log to confirm.
|
||||||
|
# GATE on the first load: `sudo systemctl restart llama.service`, then
|
||||||
|
# `./bench.py -m Ornith-1.5-35B-A3B-IQ3_XXS` — free ≥ 2.5 at the 2.0 baseline and decode
|
||||||
|
# ≈ 32 t/s → keep; else back to 24576 in all eight files (the router reads presets once at
|
||||||
|
# startup, and pi at 32k against a 24k server produces REAL overflow recoveries).
|
||||||
# ⬇ DOWNLOAD from bartowski/Ornith-1.5-35B-A3B-GGUF (imatrix; the official repo has only Q4_K_M+
|
# ⬇ DOWNLOAD from bartowski/Ornith-1.5-35B-A3B-GGUF (imatrix; the official repo has only Q4_K_M+
|
||||||
# ≥ 21.7 GB, unsloth has no 1.5 quants yet):
|
# ≥ 21.7 GB, unsloth has no 1.5 quants yet):
|
||||||
# Ornith-1.5-35B-A3B-IQ3_XXS.gguf 15,340,447,392 B · sha256 8918ccb9ee29abe3875efec0c3e86f0e35ef0b8a03e3f0e1c422869859a518d0
|
# Ornith-1.5-35B-A3B-IQ3_XXS.gguf 15,340,447,392 B · sha256 8918ccb9ee29abe3875efec0c3e86f0e35ef0b8a03e3f0e1c422869859a518d0
|
||||||
@@ -560,7 +750,9 @@ model = /home/anon/software/models/Ornith-1.5-35B-A3B-IQ3_XXS.gguf
|
|||||||
mmproj = /home/anon/software/models/mmproj-Ornith-1.5-35B-A3B-f16.gguf
|
mmproj = /home/anon/software/models/mmproj-Ornith-1.5-35B-A3B-f16.gguf
|
||||||
spec-type = draft-mtp
|
spec-type = draft-mtp
|
||||||
spec-draft-n-max = 2
|
spec-draft-n-max = 2
|
||||||
ctx-size = 24576 # = the daily driver; n_ctx_train 262144 — raise only after a bench
|
ctx-size = 32768 # 24576 → 32768 on 2026-10-22 for pi's max_tokens clamp (⚠ note above);
|
||||||
|
# UNBENCHED until the next restart — gate free ≥ 2.5 @2.0, decode unchanged;
|
||||||
|
# n_ctx_train 262144, KV is cheap here (hybrid GDN, ~8 KB/token)
|
||||||
n-gpu-layers = 99
|
n-gpu-layers = 99
|
||||||
n-cpu-moe = 16 # ✓ KEEP (09-21: 32.1 t/s, 13.8 used / 3.3 free @2.0; moe20 was 27.9 /
|
n-cpu-moe = 16 # ✓ KEEP (09-21: 32.1 t/s, 13.8 used / 3.3 free @2.0; moe20 was 27.9 /
|
||||||
# 12.4 / 4.8). = daily-driver envelope; moe14 not worth the margin.
|
# 12.4 / 4.8). = daily-driver envelope; moe14 not worth the margin.
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# vLLM on fl (docker, ROCm) — side by side with the llama.cpp router, never at the same time
|
||||||
|
|
||||||
|
Added 2026-10-21 to answer one question: **how does vLLM compare with llama.cpp on this card, on the
|
||||||
|
same models?** It is a bench rig, not a second production endpoint. Nothing here was run on fl yet —
|
||||||
|
fl has no sshd, so every command below is for you to run there; the first run will find the rough edges.
|
||||||
|
|
||||||
|
- **Box**: the same fl as the router — RX 7600 XT 16 GB (gfx1102, RDNA3), 5600X, 48 GB RAM. The card
|
||||||
|
drives the display. llama.cpp runs the Arch Vulkan build; vLLM needs ROCm, which comes **inside the
|
||||||
|
docker image** — nothing ROCm is installed on the host.
|
||||||
|
- **Files**: `vllm.ini` (presets, same `key = --flag` convention as `../llamacpp/config.ini`),
|
||||||
|
`vllmctl.py` (start/stop with the VRAM guards), `bench-vs.py` (one table for both engines).
|
||||||
|
- **Port 11348**. Not in the router, not in any client list (opencode/pi/aichat/OWUI) on purpose.
|
||||||
|
|
||||||
|
## ⚠ The one rule: one engine resident at a time
|
||||||
|
|
||||||
|
vLLM pre-allocates `gpu-memory-utilization` × 16 GiB at startup and keeps it, idle or not. The router
|
||||||
|
loads models lazily on request. So while vLLM is up, **any client request to :11343** makes the router
|
||||||
|
load a model on top of it → overcommit → GTT spill → frozen PC (the same failure `--models-max 1`
|
||||||
|
guards against). Unloading resident models is not enough; the clients on the LAN are the problem.
|
||||||
|
|
||||||
|
`vllmctl.py up` therefore refuses while `llama.service` is active. The sequence is always:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
~/.config/vllm/vllmctl.py up <preset> --stop-router # sudo systemctl stop llama.service, then start vLLM
|
||||||
|
# … use / bench vLLM …
|
||||||
|
~/.config/vllm/vllmctl.py down # stop container, wait for VRAM to drain
|
||||||
|
sudo systemctl start llama.service # router back for the LAN
|
||||||
|
```
|
||||||
|
|
||||||
|
The other order is the only one that should fail safely: vLLM compares its requested budget against
|
||||||
|
free VRAM at startup and aborts when a router model is resident ("free memory … is less than desired
|
||||||
|
GPU memory utilization" — reported by users, not something verified on this card). Everything else freezes.
|
||||||
|
|
||||||
|
## What "same models" means here, and the size surprise
|
||||||
|
|
||||||
|
llama.cpp runs GGUF; vLLM runs Hugging Face safetensors (bf16 / AWQ / GPTQ / compressed-tensors).
|
||||||
|
The GGUF files cannot be loaded natively, so the pairs below are **the same model at a vLLM-native
|
||||||
|
quant**, not the same files (same-file GGUF loading is phase 2, see the end).
|
||||||
|
|
||||||
|
Sizes from the HF API, 2026-10-21 — the "4-bit" vLLM checkpoints are **10–11.5 GB** because embeddings,
|
||||||
|
lm_head and the vision tower stay bf16. They are bigger than the Q6/Q8 GGUFs they pair with, so on
|
||||||
|
16 GB they are **clean-desktop only**, at `gpu-memory-utilization` ≈ 0.78 and 16k context:
|
||||||
|
|
||||||
|
| pair | llama.cpp preset (ledger) | vLLM preset (`vllm.ini`) | vLLM weights | fits? |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| smoke | — | `Qwen3-0.6B-smoke` = `Qwen/Qwen3-0.6B` | 1.5 GB | yes — proves docker+GPU+image |
|
||||||
|
| gemma-4-E4B | `gemma-4-E4B-it-UD-Q8_K_XL` — 57 t/s **with MTP**, 96k ctx, ~7 GB | ~~`google/gemma-4-E4B-it-qat-w4a16-ct`~~ | 11.5 GB | **✗ no** (2026-10-21): vLLM needs ~16 GiB before any KV — the bf16 per-layer embedding table (262144 × 42 × 256 × 2 B = 5.6 GB) is held twice. Tombstone in the ini. |
|
||||||
|
| Qwen3.5-9B | `Qwen3.5-9B-UD-Q6_K_XL` — 32 t/s **with MTP**, 32k ctx, 9 GB | `Qwen3.5-9B-w4a16` = `RedHatAI/Qwen3.5-9B-quantized.w4a16` | 11.0 GB | the last viable pair: 0.75 on a 2.1 GB desktop, 0.78 clean; prefill batch 512 |
|
||||||
|
|
||||||
|
**Cannot be compared on this card at all**: every 30B+ MoE (Coder-30B, GLM-4.7-Flash, Qwen3.6-35B,
|
||||||
|
Ornith, gemma-4-26B), Coder-Next 80B and Qwen3.8-27B — they only fit llama.cpp through `n-cpu-moe`
|
||||||
|
expert offload into RAM, which vLLM does not have (its MoE CPU-offload RFC died stale; the only
|
||||||
|
option is whole-weight `cpu_offload_gb`, which is slow). gpt-oss-20b (MXFP4, ~13 GB) is marginal and
|
||||||
|
its RDNA3 kernel status is unknown — not in the ini.
|
||||||
|
|
||||||
|
**Why the "docker should just work" expectation does not hold here**: of the three first-day failures,
|
||||||
|
one was the image (no gfx1102 kernels → the override, solved) and two were memory. vLLM keeps
|
||||||
|
everything in VRAM — bf16 embeddings, fp32 logits during profiling, CUDA-graph memory — on a card that
|
||||||
|
also drives the desktop. No image changes a model's memory need; the ROCm images on Docker Hub are the
|
||||||
|
right ones.
|
||||||
|
|
||||||
|
So the comparison is **not apples to apples** and the table says so: llama.cpp rows run Q8/Q6 with a
|
||||||
|
speculative MTP draft at a bigger context; vLLM rows run int4 without speculation at 16k. Read it as
|
||||||
|
"what each engine delivers on this card for this model", not as a kernel shoot-out.
|
||||||
|
|
||||||
|
## Setup on fl (once)
|
||||||
|
|
||||||
|
```fish
|
||||||
|
sudo pacman -S docker # ~20 GB free disk for the ROCm image + 25 GB for two models
|
||||||
|
sudo systemctl enable --now docker
|
||||||
|
sudo usermod -aG docker anon # then log out / in (or `newgrp docker`)
|
||||||
|
cd ~/.dots && ./install.sh # links ~/.config/vllm/* (new files are not live until re-stow)
|
||||||
|
~/.config/vllm/vllmctl.py pull # vllm/vllm-openai-rocm:v0.32.0 (ROCm 10.0; 11.6 GB download, ~25 GB on disk)
|
||||||
|
~/.config/vllm/vllmctl.py gpu-check # torch sees the card? prints device name + gfx arch
|
||||||
|
```
|
||||||
|
|
||||||
|
`gpu-check` must print `cuda available: True`, a `gfx1100` device (that is the override at work) and
|
||||||
|
`torch kernel: ok`. **The override is required** (found 2026-10-21): `v0.32.0` carries no gfx1102 code
|
||||||
|
objects, and a kernel launch without one aborts the process *silently* — the engine core vanishes, the
|
||||||
|
API server only says `Failed core proc(s): {}`, and even the `print`s before the abort are lost to
|
||||||
|
stdout buffering. `[global] env.HSA_OVERRIDE_GFX_VERSION = 11.0.0` presents the card as gfx1100 (RX 7900),
|
||||||
|
whose RDNA3 code runs on this chip. Nothing else on the host changes.
|
||||||
|
|
||||||
|
Then the smoke test (tiny model, 30 % of the card, proves the whole path before any big download —
|
||||||
|
✓ passed 2026-10-21: 154.9 t/s decode, 1462 t/s prefill, TTFT 0.02 s, 7.7 GB used incl. a 2.9 GB desktop):
|
||||||
|
|
||||||
|
```fish
|
||||||
|
~/.config/vllm/vllmctl.py up Qwen3-0.6B-smoke --stop-router
|
||||||
|
curl -s localhost:11348/v1/chat/completions -H 'content-type: application/json' \
|
||||||
|
-d '{"model":"Qwen3-0.6B-smoke","messages":[{"role":"user","content":"hi"}],"max_tokens":20}'
|
||||||
|
~/.config/vllm/vllmctl.py down; sudo systemctl start llama.service
|
||||||
|
```
|
||||||
|
|
||||||
|
First start of any model compiles Triton kernels: minutes, with `vllmctl.py up` echoing the log tail
|
||||||
|
every 30 s.
|
||||||
|
|
||||||
|
**Tags and updates.** `<tag>` is the part after the colon: `latest` = newest stable release (was
|
||||||
|
`v0.32.0` on 2026-10-18, ROCm 10.0), `nightly` = today's main branch (18 GB), `latest-rocm72` /
|
||||||
|
`nightly-rocm72` = the same on the older ROCm 7.2 stack (smaller, 12 GB), `…-base` = without vLLM
|
||||||
|
itself. The ini pins `v0.32.0` so a re-pull cannot silently change the engine under a bench run. To
|
||||||
|
update: pick the new `vX.Y.Z` on hub.docker.com/r/vllm/vllm-openai-rocm/tags, put it in `[global] image`,
|
||||||
|
`vllmctl.py pull`, then `vllmctl.py down` / `up` — the container is recreated from the image on every
|
||||||
|
`up`. Afterwards `docker image prune -f` drops the superseded layers (`docker images` shows what is kept).
|
||||||
|
`HF_TOKEN` is only needed for gated repos; none of the three presets is gated. If one ever is,
|
||||||
|
`hf auth login` on fl writes `~/.cache/huggingface/token`, which the mounted cache hands to the container.
|
||||||
|
|
||||||
|
## Running the benchmark
|
||||||
|
|
||||||
|
Two phases, because the engines cannot be resident together. Do both on a **clean desktop** (close the
|
||||||
|
browser; `vramx` must show ≤ ~1.5 GB used) — the vLLM presets leave ~2 GB free at 0.78 and an evening
|
||||||
|
desktop does not fit.
|
||||||
|
|
||||||
|
```fish
|
||||||
|
# phase 1 — vLLM (router stopped by --stop-router; first run also downloads the model)
|
||||||
|
~/.config/vllm/vllmctl.py up gemma-4-E4B-it-w4a16 --stop-router
|
||||||
|
~/.config/vllm/bench-vs.py -t vllm:gemma-4-E4B-it-w4a16 --ctx 8000 --concurrency 4
|
||||||
|
~/.config/vllm/vllmctl.py down
|
||||||
|
~/.config/vllm/vllmctl.py up Qwen3.5-9B-w4a16 # router is still stopped
|
||||||
|
~/.config/vllm/bench-vs.py -t vllm:Qwen3.5-9B-w4a16 --ctx 8000 --concurrency 4
|
||||||
|
~/.config/vllm/vllmctl.py down
|
||||||
|
|
||||||
|
# phase 2 — llama.cpp (vLLM down, router back)
|
||||||
|
sudo systemctl start llama.service
|
||||||
|
~/.config/vllm/bench-vs.py -t llamacpp:gemma-4-E4B-it-UD-Q8_K_XL -t llamacpp:Qwen3.5-9B-UD-Q6_K_XL --ctx 8000 --concurrency 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Every row lands in `bench-vs-history.md` (append) and the latest run in `bench-vs-results.md`, same
|
||||||
|
folder. The guards: a `llamacpp` target is skipped while vLLM answers `/health`; a `vllm` target is
|
||||||
|
skipped while the router has a model loaded or `llama.service` is active (`--force` overrides both).
|
||||||
|
|
||||||
|
**How to read it**
|
||||||
|
|
||||||
|
- `decode t/s` is usage tokens over wall time after the first token, measured the same way on both
|
||||||
|
engines; llama.cpp's own `timings` number is in parentheses so rows match `../llamacpp/bench-results.md`.
|
||||||
|
- Thinking is **off** on both (`chat_template_kwargs.enable_thinking=false`) and a reasoning delta counts
|
||||||
|
as first token, so TTFT compares the engines, not the preamble lengths. `--think` reverts that.
|
||||||
|
- `spec` shows the MTP draft on the llama.cpp side; vLLM rows have none unless the preset sets
|
||||||
|
`speculative-config`. Expect the gemma/Qwen llama.cpp rows to carry a 1.5–2× draft bonus.
|
||||||
|
- `agg t/s @N` is continuous batching's home turf — and llama-server still won it (85 vs 61 at 4
|
||||||
|
streams): its default slot count on b11146 is evidently > 1, so no `parallel` key is needed.
|
||||||
|
- `--ctx 8000` adds decode at an 8k prompt — the number that matters for agent loops.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
**2026-10-21, Qwen3.5-9B, same evening, same script, thinking off, 256 tok, 0.6 GB desktop:**
|
||||||
|
|
||||||
|
| engine · checkpoint | spec | decode t/s | prefill t/s | TTFT s | decode @8k prompt | 4 streams agg t/s | VRAM used |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| vLLM · RedHatAI w4a16 @0.80, 16k, batch 512 | — | 29.4 | **181** | **0.17** | 16.0 | 60.6 | 12.5 GB |
|
||||||
|
| llama.cpp · UD-Q6_K_XL, 32k | MTP draft | **61.3** | 114 | 0.27 | **51.1** | **85.4** | 10.9 GB |
|
||||||
|
|
||||||
|
What it says, for this card:
|
||||||
|
|
||||||
|
- **Decode**: llama.cpp wins 2× single-stream and 3× at an 8k prompt. Most of the 2× is the MTP draft
|
||||||
|
(the ledger's pre-MTP Q8 number was 17 t/s in July; a no-draft Q6 today is probably ~30, i.e. parity
|
||||||
|
with vLLM). The 8k gap is not MTP: vLLM's decode halves with context here (16.0 vs 29.4) while
|
||||||
|
llama.cpp loses 17 % — on RDNA3 vLLM's attention path is the Triton generic one, llama.cpp's is a
|
||||||
|
tuned Vulkan kernel.
|
||||||
|
- **Prefill**: vLLM +59 % even with its batch capped at 512 for memory. This is its real strength on
|
||||||
|
the box, and it shows in TTFT.
|
||||||
|
- **Concurrency**: the column that was supposed to be vLLM's — it batched 4 streams to 2× its single
|
||||||
|
(60.6), but llama-server served the same 4 at 85.4. Its default slot count is evidently > 1 on this
|
||||||
|
build, so the README's "llama.cpp serializes" caveat does not hold; it does not need a `parallel` key.
|
||||||
|
- **Memory**: vLLM holds 1.6 GB more for a model with 2 GB less context, and only runs on a ≤ 0.6 GB
|
||||||
|
desktop. 0.75 was 0.11 GiB short of KV room; 0.80 is the setting.
|
||||||
|
|
||||||
|
**Verdict**: no vLLM model earns a roster slot on fl; the bench rig stays, the router stays, llama-swap
|
||||||
|
is not needed. The one follow-up that could move the decode column is MTP in vLLM (the knob is in
|
||||||
|
the ini) — but it cannot touch the memory column or the MoE roster, so it is curiosity, not a decision.
|
||||||
|
|
||||||
|
## When it does not start
|
||||||
|
|
||||||
|
- **`No available memory for the cache blocks`** (seen 2026-10-21, gemma @0.73/8k on a 2.1 GB desktop):
|
||||||
|
the model loaded, but budget − weights − activation reservations left nothing for KV. What vLLM
|
||||||
|
reserved is in the log: `docker logs vllm 2>&1 | grep -iE 'profil|kv cache|weights take|peak'`.
|
||||||
|
Cheapest fixes first: text-only (`limit-mm-per-prompt` all zero — skips the image/audio encoder
|
||||||
|
warmup, now in both presets), smaller `max-num-batched-tokens` (2048, now in both presets), lower
|
||||||
|
`max-num-seqs`; then a cleaner desktop so the projection allows 0.78–0.82; last resort the smaller
|
||||||
|
GPTQ checkpoint. `--set key=val` tries any of these without editing the ini.
|
||||||
|
- **`ABORT: projected free …`**: the desktop is using more than the preset assumes. Close GPU apps or
|
||||||
|
take the suggested utilization it prints. `--force` exists; the freeze is real.
|
||||||
|
- **compressed-tensors / "no kernel" for int4 on ROCm**: both real presets use compressed-tensors
|
||||||
|
pack-quantized. If this image lacks a ROCm kernel for it, swap in the GPTQ alternative noted in the
|
||||||
|
ini (`Vishva007/gemma-4-E4B-it-W4A16-AutoRound-GPTQ`, 10.0 GB) — different kernel path.
|
||||||
|
- **Hangs / garbage output**: RDNA3 attention backend. Set `env.VLLM_ATTENTION_BACKEND = TRITON_ATTN`
|
||||||
|
explicitly; option names moved between versions — `vllmctl.py exec vllm serve --help` shows this image's.
|
||||||
|
- **GTT growing during a run** (`vramx`, the GTT line): the budget is leaking into system RAM — stop,
|
||||||
|
lower the utilization. Do not wait for the desktop to stutter.
|
||||||
|
- **Root-owned files in `~/.cache/huggingface`**: the container runs as root, so its downloads land
|
||||||
|
root-owned in anon's HF cache. Harmless for the rig; `sudo chown -R anon: ~/.cache/huggingface` if
|
||||||
|
the host-side `hf` tooling complains.
|
||||||
|
- **Container died**: `docker logs vllm` (the container is kept after a crash; `vllmctl.py up` shows
|
||||||
|
the tail and removes it on the next start).
|
||||||
|
|
||||||
|
## Phase 2 — if the numbers justify more
|
||||||
|
|
||||||
|
1. **Same-file GGUF in vLLM**: GGUF loading moved to an out-of-tree `vllm-gguf-plugin`; it is
|
||||||
|
"highly experimental and under-optimized" per vLLM's docs and needs a custom image layer
|
||||||
|
(`pip install vllm-gguf-plugin`), plus `--tokenizer <base repo>`. Untested on ROCm. It would make the
|
||||||
|
weights identical — and show vLLM's unoptimized path, so interpret carefully.
|
||||||
|
2. **MTP in vLLM**: `RedHatAI/Qwen3.5-9B-quantized.w4a16` ships `model_mtp.safetensors`; the
|
||||||
|
commented `speculative-config` in the ini is the knob (syntax per this image's `--help`).
|
||||||
|
3. **Permanent coexistence**: replace the router's `--models-preset` mode with llama-swap (AUR
|
||||||
|
`llama-swap-bin`), which spawns llama-server *or* the vLLM container per model id, one at a time,
|
||||||
|
with a `ttl` idle unload. Same host/port/model ids for every client. Only worth it if a vLLM model
|
||||||
|
earns a place in the daily roster.
|
||||||
Executable
+258
@@ -0,0 +1,258 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
bench-vs — the SAME prompt through llama.cpp (router :11343) and vLLM (:11348), one table.
|
||||||
|
|
||||||
|
Measures per target: decode t/s, prefill t/s, TTFT, VRAM used/free (amdgpu sysfs, no root needed),
|
||||||
|
optionally decode at a long prompt (--ctx) and aggregate throughput under N parallel requests
|
||||||
|
(--concurrency). Method = ../llamacpp/bench.py (streamed chat completion, usage.completion_tokens
|
||||||
|
over wall time after the first token), applied identically to both engines; llama.cpp's own
|
||||||
|
`timings` figure is shown in parentheses so rows stay comparable with bench-results.md.
|
||||||
|
|
||||||
|
Thinking is OFF by default on both engines (chat_template_kwargs.enable_thinking=false) and the
|
||||||
|
first reasoning_content delta counts as the first token anyway — a reasoning preamble of different
|
||||||
|
length per engine would otherwise poison TTFT. --think re-enables it.
|
||||||
|
|
||||||
|
GUARDS (the card drives the display; two resident engines = GTT spill = frozen PC):
|
||||||
|
- a llamacpp target is refused while vLLM answers /health (stop it: ./vllmctl.py down)
|
||||||
|
- a vllm target is refused while the router reports a loaded model or llama.service is active
|
||||||
|
|
||||||
|
./bench-vs.py -t vllm:gemma-4-E4B-it-w4a16 # vLLM up (vllmctl.py up …), router stopped
|
||||||
|
./bench-vs.py -t llamacpp:gemma-4-E4B-it-UD-Q8_K_XL # vLLM down, router started
|
||||||
|
./bench-vs.py -t llamacpp:Qwen3.5-9B-UD-Q6_K_XL --ctx 8000 --concurrency 4
|
||||||
|
Output: bench-vs-results.md (latest) + bench-vs-history.md (append), next to this script. Stdlib only.
|
||||||
|
"""
|
||||||
|
import argparse, json, os, re, subprocess, sys, threading, time, urllib.request, urllib.error
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
DRM = "/sys/class/drm/renderD128/device"
|
||||||
|
URLS = {"llamacpp": "http://127.0.0.1:11343/v1", "vllm": "http://127.0.0.1:11348/v1"}
|
||||||
|
TASK = "Write a Python function that merges two sorted lists, with a short docstring and one example."
|
||||||
|
|
||||||
|
|
||||||
|
def sysfs(name):
|
||||||
|
try:
|
||||||
|
return int(open(os.path.join(DRM, name)).read().strip())
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def gb(b):
|
||||||
|
return f"{b / 1e9:.1f} GB" if b is not None else "-"
|
||||||
|
|
||||||
|
|
||||||
|
def http(url, key, payload=None, timeout=600):
|
||||||
|
data = json.dumps(payload).encode() if payload is not None else None
|
||||||
|
hdrs = {"Authorization": f"Bearer {key}"}
|
||||||
|
if data:
|
||||||
|
hdrs["Content-Type"] = "application/json"
|
||||||
|
return urllib.request.urlopen(urllib.request.Request(url, data=data, headers=hdrs), timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ini(path):
|
||||||
|
sections, sec = {}, None
|
||||||
|
try:
|
||||||
|
lines = open(path, encoding="utf-8").read().splitlines()
|
||||||
|
except OSError:
|
||||||
|
return {}
|
||||||
|
for ln in lines:
|
||||||
|
s = ln.strip()
|
||||||
|
if not s or s.startswith("#"):
|
||||||
|
continue
|
||||||
|
if s.startswith("[") and s.endswith("]"):
|
||||||
|
sec = s[1:-1]; sections[sec] = {}
|
||||||
|
elif sec and "=" in s:
|
||||||
|
k, v = s.split("=", 1)
|
||||||
|
sections[sec][k.strip()] = v.split(" #", 1)[0].split("\t#", 1)[0].strip()
|
||||||
|
return sections
|
||||||
|
|
||||||
|
|
||||||
|
def run(base, key, model, prompt, n, temp, timeout, think):
|
||||||
|
payload = {"model": model, "messages": [{"role": "user", "content": prompt}],
|
||||||
|
"max_tokens": n, "temperature": temp, "stream": True,
|
||||||
|
"stream_options": {"include_usage": True},
|
||||||
|
"timings_per_token": True} # llama.cpp extension, ignored by vLLM
|
||||||
|
if not think:
|
||||||
|
payload["chat_template_kwargs"] = {"enable_thinking": False} # both engines accept it; templates without it ignore it
|
||||||
|
t0 = time.perf_counter(); ttft = None; ntok = 0; usage = None; timings = None
|
||||||
|
for raw in http(base.rstrip("/") + "/chat/completions", key, payload, timeout):
|
||||||
|
line = raw.decode("utf-8", "ignore").strip()
|
||||||
|
if not line.startswith("data:"):
|
||||||
|
continue
|
||||||
|
body = line[5:].strip()
|
||||||
|
if body == "[DONE]":
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
c = json.loads(body)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
ch = c.get("choices") or [{}]
|
||||||
|
d = ch[0].get("delta", {}) if ch else {}
|
||||||
|
if d.get("content") or d.get("reasoning_content") or d.get("reasoning"):
|
||||||
|
if ttft is None:
|
||||||
|
ttft = time.perf_counter() - t0
|
||||||
|
ntok += 1
|
||||||
|
if c.get("usage"):
|
||||||
|
usage = c["usage"]
|
||||||
|
if c.get("timings"):
|
||||||
|
timings = c["timings"]
|
||||||
|
total = time.perf_counter() - t0
|
||||||
|
comp = (usage or {}).get("completion_tokens") or ntok
|
||||||
|
ptok = (usage or {}).get("prompt_tokens")
|
||||||
|
gen_s = total - (ttft or total)
|
||||||
|
tg = comp / gen_s if gen_s > 0 else 0.0
|
||||||
|
pp = (ptok / ttft) if (ptok and ttft) else None
|
||||||
|
srv = timings.get("predicted_per_second") if timings else None # llama.cpp's own number, for the ledger
|
||||||
|
return {"tg": tg, "pp": pp, "ttft": ttft or 0.0, "ptok": ptok or 0, "ctok": comp, "wall": total, "srv": srv,
|
||||||
|
"t0": t0, "t1": t0 + total}
|
||||||
|
|
||||||
|
|
||||||
|
def with_retries(fn, retries, wait, label=""):
|
||||||
|
last = None
|
||||||
|
for i in range(retries + 1):
|
||||||
|
try:
|
||||||
|
return fn()
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode("utf-8", "ignore")[:300]
|
||||||
|
if e.code < 500 or "failed to load" in body.lower():
|
||||||
|
raise
|
||||||
|
last = e
|
||||||
|
except (urllib.error.URLError, ConnectionError, TimeoutError) as e:
|
||||||
|
last = e
|
||||||
|
if i < retries:
|
||||||
|
print(f" … {label}: not ready ({last}); retry in {wait}s", file=sys.stderr)
|
||||||
|
time.sleep(wait)
|
||||||
|
raise last
|
||||||
|
|
||||||
|
|
||||||
|
def vllm_up(url):
|
||||||
|
try:
|
||||||
|
return http(re.sub(r"/v1/?$", "", url) + "/health", "x", timeout=3).status == 200
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _status(m):
|
||||||
|
"""Router /models status: newer builds send {'value': 'loaded'|'sleeping'|'unloaded'|…}, older a string."""
|
||||||
|
st = m.get("status", "loaded")
|
||||||
|
return st.get("value", "loaded") if isinstance(st, dict) else st
|
||||||
|
|
||||||
|
|
||||||
|
def router_loaded(url):
|
||||||
|
try:
|
||||||
|
d = json.load(http(re.sub(r"/v1/?$", "", url) + "/models", "x", timeout=3))
|
||||||
|
return [m["id"] for m in d.get("data", []) if _status(m) in ("loaded", "loading")]
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def router_active():
|
||||||
|
try:
|
||||||
|
r = subprocess.run(["systemctl", "is-active", "llama.service"], capture_output=True, text=True, timeout=10)
|
||||||
|
except (OSError, subprocess.TimeoutExpired):
|
||||||
|
return False
|
||||||
|
return r.stdout.strip() == "active"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("-t", "--target", action="append", required=True,
|
||||||
|
help="engine:model, engine = llamacpp | vllm (repeatable)")
|
||||||
|
ap.add_argument("--url", action="append", default=[], help="engine=URL override, e.g. vllm=http://host:11348/v1")
|
||||||
|
ap.add_argument("-k", "--key", default="no-key-required")
|
||||||
|
ap.add_argument("-n", "--tokens", type=int, default=256)
|
||||||
|
ap.add_argument("--temp", type=float, default=0.3)
|
||||||
|
ap.add_argument("--ctx", type=int, default=0, help="also record decode at ~this many prompt tokens")
|
||||||
|
ap.add_argument("--concurrency", type=int, default=0, help="also record aggregate t/s with N parallel requests")
|
||||||
|
ap.add_argument("--think", action="store_true", help="leave the model's thinking ON (default: off on both engines)")
|
||||||
|
ap.add_argument("--timeout", type=int, default=600)
|
||||||
|
ap.add_argument("--retries", type=int, default=4); ap.add_argument("--retry-wait", type=int, default=15)
|
||||||
|
ap.add_argument("--force", action="store_true", help="skip the two-engines-resident guards")
|
||||||
|
ap.add_argument("--out", default=os.path.join(HERE, "bench-vs-results.md"))
|
||||||
|
ap.add_argument("--history", default=os.path.join(HERE, "bench-vs-history.md"))
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
urls = dict(URLS)
|
||||||
|
for u in a.url:
|
||||||
|
k, v = u.split("=", 1); urls[k] = v
|
||||||
|
llcfg = parse_ini(os.path.join(HERE, "..", "llamacpp", "config.ini"))
|
||||||
|
vlcfg = parse_ini(os.path.join(HERE, "vllm.ini"))
|
||||||
|
total = sysfs("mem_info_vram_total")
|
||||||
|
if total is None:
|
||||||
|
print(" (amdgpu sysfs not readable here — VRAM columns are OFF; run this ON fl for them)", file=sys.stderr)
|
||||||
|
|
||||||
|
filler = ("The quick brown fox jumps over the lazy dog. " * max(1, a.ctx // 9)) if a.ctx else None
|
||||||
|
cols = ["engine", "model", "cfg ctx", "spec", "decode t/s", "prefill t/s", "TTFT s", "VRAM used", "VRAM free"]
|
||||||
|
if a.ctx:
|
||||||
|
cols.append(f"decode@{a.ctx // 1000}k")
|
||||||
|
if a.concurrency:
|
||||||
|
cols.append(f"agg t/s @{a.concurrency}")
|
||||||
|
|
||||||
|
latest = open(a.out, "w", encoding="utf-8"); hist = open(a.history, "a", encoding="utf-8")
|
||||||
|
|
||||||
|
def emit(line):
|
||||||
|
print(line); latest.write(line + "\n"); latest.flush(); hist.write(line + "\n"); hist.flush()
|
||||||
|
|
||||||
|
latest.write("# llama.cpp vs vLLM (latest run)\n\n"); hist.write(f"\n## run {datetime.now():%Y-%m-%d %H:%M}\n\n")
|
||||||
|
emit(f"_Run {datetime.now():%Y-%m-%d %H:%M} · gen {a.tokens} tok · temp {a.temp} · thinking "
|
||||||
|
f"{'ON' if a.think else 'OFF'} · VRAM total {gb(total)} · baseline used {gb(sysfs('mem_info_vram_used'))} · "
|
||||||
|
f"decode = usage tokens / wall after first token on BOTH engines (llama.cpp's own timings in parentheses)_\n")
|
||||||
|
emit("| " + " | ".join(cols) + " |"); emit("|" + "|".join(["---"] * len(cols)) + "|")
|
||||||
|
|
||||||
|
for t in a.target:
|
||||||
|
eng, _, model = t.partition(":")
|
||||||
|
if eng not in urls or not model:
|
||||||
|
emit(f"| {eng} | {model} | bad target — use engine:model with engine in {list(urls)} |"); continue
|
||||||
|
base = urls[eng]
|
||||||
|
# guards
|
||||||
|
if not a.force:
|
||||||
|
if eng == "llamacpp" and vllm_up(urls["vllm"]):
|
||||||
|
emit(f"| llamacpp | {model} | ⚠ SKIPPED: vLLM is up on {urls['vllm']} — loading a router model on top = freeze. "
|
||||||
|
f"`vllmctl.py down` first |"); continue
|
||||||
|
if eng == "vllm":
|
||||||
|
loaded = router_loaded(urls["llamacpp"])
|
||||||
|
if loaded or router_active():
|
||||||
|
emit(f"| vllm | {model} | ⚠ SKIPPED: router loaded={loaded or '-'} active={router_active()} — "
|
||||||
|
f"stop llama.service before benching vLLM |"); continue
|
||||||
|
if eng == "llamacpp":
|
||||||
|
c = llcfg.get(model, {})
|
||||||
|
cfgctx, spec = c.get("ctx-size", "-"), (c.get("spec-type") or "-")
|
||||||
|
else:
|
||||||
|
c = vlcfg.get(model, {})
|
||||||
|
cfgctx = c.get("max-model-len", "-")
|
||||||
|
spec = "mtp" if c.get("speculative-config") else "-"
|
||||||
|
cells = [eng, model, cfgctx, spec]
|
||||||
|
try:
|
||||||
|
with_retries(lambda: run(base, a.key, model, "hi", 8, a.temp, a.timeout, a.think), a.retries, a.retry_wait, model)
|
||||||
|
used = sysfs("mem_info_vram_used")
|
||||||
|
r = with_retries(lambda: run(base, a.key, model, TASK, a.tokens, a.temp, a.timeout, a.think), a.retries, a.retry_wait, model)
|
||||||
|
dec = f"{r['tg']:.1f}" + (f" ({r['srv']:.1f})" if r["srv"] else "")
|
||||||
|
cells += [dec, f"{r['pp']:.0f}" if r["pp"] else "-", f"{r['ttft']:.2f}", gb(used),
|
||||||
|
gb(total - used) if (total and used is not None) else "-"]
|
||||||
|
if a.ctx:
|
||||||
|
rc = run(base, a.key, model, filler + "\n\n" + TASK, a.tokens, a.temp, a.timeout, a.think)
|
||||||
|
cells.append(f"{rc['tg']:.1f}")
|
||||||
|
if a.concurrency:
|
||||||
|
res = [None] * a.concurrency
|
||||||
|
def worker(i):
|
||||||
|
res[i] = run(base, a.key, model, TASK + f" (variant {i})", a.tokens, a.temp, a.timeout, a.think)
|
||||||
|
th = [threading.Thread(target=worker, args=(i,)) for i in range(a.concurrency)]
|
||||||
|
for x in th: x.start()
|
||||||
|
for x in th: x.join()
|
||||||
|
ok = [x for x in res if x]
|
||||||
|
wall = max(x["t1"] for x in ok) - min(x["t0"] for x in ok)
|
||||||
|
cells.append(f"{sum(x['ctok'] for x in ok) / wall:.1f}" if wall > 0 else "-")
|
||||||
|
except Exception as e:
|
||||||
|
err = f"HTTP {e.code} {e.read().decode('utf-8', 'ignore')[:120]!r}" if isinstance(e, urllib.error.HTTPError) else f"ERROR {e}"
|
||||||
|
cells = cells[:4] + [err] + [""] * (len(cols) - 5) # same width whether it died early or in the --ctx/--concurrency pass
|
||||||
|
emit("| " + " | ".join(str(x) for x in cells) + " |")
|
||||||
|
|
||||||
|
latest.write("\n_llama.cpp rows run with the preset's MTP draft when `spec` says so; vLLM rows have no speculation "
|
||||||
|
"unless `spec` = mtp. `agg t/s @N` = N parallel requests, completion tokens over the wall time of "
|
||||||
|
"the whole batch (llama-server's default slot count serves them in parallel too)._\n")
|
||||||
|
latest.close(); hist.close()
|
||||||
|
print(f"\nwrote {a.out} (+ appended {a.history})")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# vLLM presets for fl — AMD RX 7600 XT · 16 GB · docker image vllm/vllm-openai-rocm
|
||||||
|
# Same convention as ../llamacpp/config.ini: section name = served model id, `key = value` = `--key value`.
|
||||||
|
#
|
||||||
|
# ⚠ ONE ENGINE AT A TIME. vLLM pre-allocates `gpu-memory-utilization` × 16 GiB at startup and KEEPS it.
|
||||||
|
# Any llama.cpp load on top (a client hitting :11343 is enough) overcommits → GTT spill → PC freeze.
|
||||||
|
# `vllmctl.py up` refuses to start while llama.service is active — stop the router first
|
||||||
|
# (`vllmctl.py up <preset> --stop-router`), start it again after `vllmctl.py down`.
|
||||||
|
#
|
||||||
|
# Sections:
|
||||||
|
# [global] container/runtime knobs (NOT vllm flags) — see vllmctl.py
|
||||||
|
# [<preset>] one `vllmctl.py up <preset>`:
|
||||||
|
# model HF repo id, or /models/<dir> (models-dir is mounted at /models, read-only)
|
||||||
|
# any other key → `--key value` for `vllm serve`; `true` → bare `--key`; `false` → omitted
|
||||||
|
# served-model-name defaults to the section name (what clients and bench-vs.py use)
|
||||||
|
# x-* vllmctl-only metadata (x-weights-gb feeds the VRAM projection)
|
||||||
|
# env.NAME = value container environment (per preset; [global] env.* apply to all)
|
||||||
|
#
|
||||||
|
# VRAM MATH (vllmctl prints it before every start, aborts under reserve-gb unless --force):
|
||||||
|
# budget = gpu-memory-utilization × 17.16 GB (vLLM's total = the card's total, same as sysfs)
|
||||||
|
# budget must hold weights + activations + KV cache; everything above it + the desktop must stay
|
||||||
|
# under 16 GB with ≥ reserve-gb free. 0.75 → 12.9 GB budget → ~2.9 GB free on a 1.3 GB clean desktop.
|
||||||
|
# A busy evening desktop (3+ GB) does NOT fit any of the real models below — clean desktop only.
|
||||||
|
# Sizes measured 2026-10-21 from the HF API: the 4-bit vLLM checkpoints are 10-11.5 GB because
|
||||||
|
# embeddings, lm_head and the vision tower stay bf16 — bigger than the Q6/Q8 GGUFs they pair with.
|
||||||
|
|
||||||
|
[global]
|
||||||
|
image = vllm/vllm-openai-rocm:v0.32.0 # = `latest` on 2026-10-18 (ROCm 10.0, 11.6 GB download); update = bump this + `vllmctl.py pull`
|
||||||
|
container = vllm
|
||||||
|
port = 11348 # whisper = 11345/11346, kokoro = 11347
|
||||||
|
models-dir = /home/anon/software/models # mounted at /models (ro) — share the GGUF dir
|
||||||
|
hf-cache = /home/anon/.cache/huggingface # mounted at /root/.cache/huggingface (downloads + token)
|
||||||
|
router = http://127.0.0.1:11343 # llama.cpp router root (for the resident-model gate)
|
||||||
|
reserve-gb = 2.0 # abort if projected free VRAM < this (bench.py floor 1.5, target 2.5)
|
||||||
|
drain-gb = 4.0 # "card is clean" threshold before starting (same as bench.py)
|
||||||
|
health-timeout = 1800 # s — first start compiles Triton kernels, be patient
|
||||||
|
gpu-memory-utilization = 0.75 # default; presets override
|
||||||
|
# REQUIRED on this card (✓ verified 2026-10-21): v0.32.0 ships no gfx1102 code objects — without the
|
||||||
|
# override every GPU kernel launch aborts the process SILENTLY (the engine core just vanishes, the
|
||||||
|
# API server reports "Failed core proc(s): {}"). gfx1100 (RX 7900) code runs on gfx1102, same RDNA3 ISA.
|
||||||
|
env.HSA_OVERRIDE_GFX_VERSION = 11.0.0
|
||||||
|
# Attention backend knobs (ROCm; names moved between versions — `vllmctl.py exec vllm serve --help`):
|
||||||
|
# env.VLLM_ATTENTION_BACKEND = TRITON_ATTN # the default on RDNA; set explicitly if a hang points at attention
|
||||||
|
|
||||||
|
# ─── 1. smoke test — tiny, certain to be supported; proves docker + GPU + image + override ────
|
||||||
|
# ✓ 2026-10-21: 154.9 t/s decode · 1462 t/s prefill · TTFT 0.02 s · 7.7 GB used incl. a 2.9 GB desktop.
|
||||||
|
[Qwen3-0.6B-smoke]
|
||||||
|
model = Qwen/Qwen3-0.6B
|
||||||
|
gpu-memory-utilization = 0.30
|
||||||
|
max-model-len = 4096
|
||||||
|
x-weights-gb = 1.5
|
||||||
|
|
||||||
|
# ─── 2. gemma-4-E4B — ✗ DOES NOT FIT vLLM ON THIS CARD (2026-10-21), tombstone kept for the math ──
|
||||||
|
# Pairs with llama.cpp [gemma-4-E4B-it-UD-Q8_K_XL] (57 t/s with MTP, 96k ctx). google/gemma-4-E4B-it-qat-w4a16-ct
|
||||||
|
# is 11.5 GB, and the second try (text-only, batch 2048, @0.73 on a 2.1 GB desktop) ended with
|
||||||
|
# "Available KV cache memory: -4.44 GiB" after an OOM on a 5,637,144,576-byte allocation. That is exactly
|
||||||
|
# Gemma 4's per-layer embedding table: 262144 vocab × 42 layers × 256 × 2 bytes (bf16). vLLM holds it
|
||||||
|
# in bf16 AND needs a second buffer of the same size → ~16 GiB before any KV block. No util, batch or
|
||||||
|
# desktop fixes that; the GPTQ/AWQ alternatives carry the same bf16 table. llama.cpp fits because the
|
||||||
|
# GGUF stores the table quantized, once. A different image changes nothing here.
|
||||||
|
# [gemma-4-E4B-it-w4a16]
|
||||||
|
# model = google/gemma-4-E4B-it-qat-w4a16-ct
|
||||||
|
# gpu-memory-utilization = 0.78
|
||||||
|
# max-model-len = 16384
|
||||||
|
# max-num-seqs = 4
|
||||||
|
# max-num-batched-tokens = 2048
|
||||||
|
# limit-mm-per-prompt = {"image": 0, "audio": 0, "video": 0}
|
||||||
|
# x-weights-gb = 11.5
|
||||||
|
|
||||||
|
# ─── 3. Qwen3.5-9B — pairs with llama.cpp [Qwen3.5-9B-UD-Q6_K_XL] (32 t/s with MTP, 32k ctx) ──
|
||||||
|
# Red Hat's llm-compressor int4 (compressed-tensors). 10.95 GB + a 0.49 GB MTP head file.
|
||||||
|
# Alternative (real AWQ, Triton kernels on ROCm): QuantTrio/Qwen3.5-9B-AWQ — 12.4 GB, too big for 0.78.
|
||||||
|
# The last viable "same model" pair: no per-layer table, but a 248k vocab → the fp32 logits buffer
|
||||||
|
# during profiling is max-num-batched-tokens × 248k × 4 B (2048 → 2.0 GB, 512 → 0.5 GB). Start small.
|
||||||
|
# On a 2.1 GB desktop the projection allows 0.75 (`--set gpu-memory-utilization=0.75`) → ~1.9 GB for
|
||||||
|
# activations + graphs + KV. If "No available memory for the cache blocks" again: `--set enforce-eager=true`
|
||||||
|
# (drops the CUDA-graph memory, costs decode speed), then a cleaner desktop for 0.78.
|
||||||
|
# ✓ RUNS 2026-10-21 @0.80 (0.75 was 0.11 GiB short of KV room): 29.4 t/s decode · 181 prefill · TTFT 0.17 ·
|
||||||
|
# decode@8k 16.0 · 4-stream aggregate 60.6 · 12.5 GB used on a 0.6 GB desktop. Same evening, llama.cpp
|
||||||
|
# [Qwen3.5-9B-UD-Q6_K_XL] with its MTP draft: 61.3 · 114 · 0.27 · 51.1 · 85.4 · 10.9 GB. See README.
|
||||||
|
[Qwen3.5-9B-w4a16]
|
||||||
|
model = RedHatAI/Qwen3.5-9B-quantized.w4a16
|
||||||
|
gpu-memory-utilization = 0.80 # needs a ≤ 0.6 GB desktop (projection: 2.9 GB free); 0.75 fails on KV room
|
||||||
|
max-model-len = 16384
|
||||||
|
max-num-seqs = 4
|
||||||
|
max-num-batched-tokens = 512 # caps the profiling logits buffer (prefill t/s cost — note it in the ledger)
|
||||||
|
limit-mm-per-prompt = {"image": 0, "video": 0} # text-only bench (Qwen3.5 has no audio input)
|
||||||
|
x-weights-gb = 11.0
|
||||||
|
# NEXT (the one knob that could change the verdict): MTP speculative decoding — the repo ships
|
||||||
|
# model_mtp.safetensors and llama.cpp's 2× comes from exactly this. Costs ~0.5 GB weights + draft
|
||||||
|
# state → try @0.82 on a clean desktop. Syntax per `vllmctl.py exec vllm serve --help`:
|
||||||
|
# ./vllmctl.py up Qwen3.5-9B-w4a16 --set gpu-memory-utilization=0.82 --set 'speculative-config={"method": "mtp", "num_speculative_tokens": 2}'
|
||||||
|
# speculative-config = {"method": "mtp", "num_speculative_tokens": 2}
|
||||||
Executable
+398
@@ -0,0 +1,398 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
vllmctl — run vLLM (docker, ROCm) on fl WITHOUT freezing the box.
|
||||||
|
|
||||||
|
The RX 7600 XT drives the display; VRAM overcommit spills to GTT and freezes the PC. vLLM grabs
|
||||||
|
`gpu-memory-utilization` × total VRAM at startup and keeps it, so this script gates every start:
|
||||||
|
|
||||||
|
1. llama.service must be STOPPED (or --stop-router): a LAN client hitting :11343 would make the
|
||||||
|
router load a model on top of vLLM = the freeze. --unsafe-keep-router skips this gate.
|
||||||
|
2. the card must be clean (VRAM used ≤ drain-gb; resident router models are unloaded via its API).
|
||||||
|
3. projected free VRAM = total − used − budget must be ≥ reserve-gb (else abort, --force overrides).
|
||||||
|
|
||||||
|
Presets live in vllm.ini next to this file (same key = --flag convention as llamacpp/config.ini).
|
||||||
|
|
||||||
|
./vllmctl.py up <preset> [--stop-router] [--force] [--dry-run] start, wait for /health
|
||||||
|
./vllmctl.py down stop, wait for VRAM to drain
|
||||||
|
./vllmctl.py status | logs | pull | gpu-check | exec <cmd...>
|
||||||
|
Stdlib only. Run ON fl.
|
||||||
|
"""
|
||||||
|
import argparse, grp, json, os, re, shlex, subprocess, sys, time, urllib.request, urllib.error
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
INI = os.path.join(HERE, "vllm.ini")
|
||||||
|
DRM = "/sys/class/drm/renderD128/device"
|
||||||
|
|
||||||
|
|
||||||
|
# ─── ini ────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
def parse_ini(path):
|
||||||
|
"""{section: {key: value}} — inline `# comments` stripped, order kept."""
|
||||||
|
sections, sec = {}, None
|
||||||
|
for ln in open(path, encoding="utf-8").read().splitlines():
|
||||||
|
s = ln.strip()
|
||||||
|
if not s or s.startswith("#"):
|
||||||
|
continue
|
||||||
|
if s.startswith("[") and s.endswith("]"):
|
||||||
|
sec = s[1:-1]; sections[sec] = {}
|
||||||
|
elif sec and "=" in s:
|
||||||
|
k, v = s.split("=", 1)
|
||||||
|
sections[sec][k.strip()] = v.split(" #", 1)[0].split("\t#", 1)[0].strip()
|
||||||
|
return sections
|
||||||
|
|
||||||
|
|
||||||
|
def build_args(preset):
|
||||||
|
"""vllm serve args from a preset section (x-* and env.* are not flags)."""
|
||||||
|
args = []
|
||||||
|
for k, v in preset.items():
|
||||||
|
if k.startswith("x-") or k.startswith("env."):
|
||||||
|
continue
|
||||||
|
if v.lower() == "false":
|
||||||
|
continue
|
||||||
|
if v.lower() == "true":
|
||||||
|
args.append(f"--{k}")
|
||||||
|
else:
|
||||||
|
args += [f"--{k}", v]
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
# ─── gpu / router ───────────────────────────────────────────────────────────────────────────
|
||||||
|
def sysfs(name):
|
||||||
|
try:
|
||||||
|
return int(open(os.path.join(DRM, name)).read().strip())
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def vram():
|
||||||
|
"""(used, total, gtt_used) bytes from amdgpu sysfs; None where unreadable."""
|
||||||
|
return sysfs("mem_info_vram_used"), sysfs("mem_info_vram_total"), sysfs("mem_info_gtt_used")
|
||||||
|
|
||||||
|
|
||||||
|
def gb(b):
|
||||||
|
return f"{b / 1e9:.1f} GB" if b is not None else "-"
|
||||||
|
|
||||||
|
|
||||||
|
def http(url, payload=None, timeout=10):
|
||||||
|
data = json.dumps(payload).encode() if payload is not None else None
|
||||||
|
hdrs = {"Content-Type": "application/json"} if data else {}
|
||||||
|
return urllib.request.urlopen(urllib.request.Request(url, data=data, headers=hdrs), timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
def _status(m):
|
||||||
|
"""Router /models status: newer builds send {'value': 'loaded'|'sleeping'|'unloaded'|…}, older a string."""
|
||||||
|
st = m.get("status", "loaded")
|
||||||
|
return st.get("value", "loaded") if isinstance(st, dict) else st
|
||||||
|
|
||||||
|
|
||||||
|
def router_loaded(root):
|
||||||
|
"""ids the llama.cpp router reports as loaded ([] if unreachable)."""
|
||||||
|
try:
|
||||||
|
d = json.load(http(root.rstrip("/") + "/models", timeout=5))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
return [m["id"] for m in d.get("data", []) if _status(m) in ("loaded", "loading")]
|
||||||
|
|
||||||
|
|
||||||
|
def router_unload(root):
|
||||||
|
for m in router_loaded(root):
|
||||||
|
try:
|
||||||
|
http(root.rstrip("/") + "/models/unload", {"model": m}, timeout=30).read()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def router_active():
|
||||||
|
try:
|
||||||
|
r = subprocess.run(["systemctl", "is-active", "llama.service"], capture_output=True, text=True, timeout=10)
|
||||||
|
except (OSError, subprocess.TimeoutExpired):
|
||||||
|
return False
|
||||||
|
return r.stdout.strip() == "active"
|
||||||
|
|
||||||
|
|
||||||
|
def router_hint():
|
||||||
|
return "" if router_active() else "\n (llama.service is STOPPED → sudo systemctl start llama.service to bring the router back)"
|
||||||
|
|
||||||
|
|
||||||
|
def wait_drain(drain_gb, window_s, label):
|
||||||
|
used, _, _ = vram()
|
||||||
|
end = time.time() + window_s
|
||||||
|
said = False
|
||||||
|
while used is not None and used > drain_gb * 1e9 and time.time() < end:
|
||||||
|
if not said:
|
||||||
|
print(f" … waiting for VRAM to drain {label} ({gb(used)} used)"); said = True
|
||||||
|
time.sleep(5)
|
||||||
|
used, _, _ = vram()
|
||||||
|
return used
|
||||||
|
|
||||||
|
|
||||||
|
# ─── docker ─────────────────────────────────────────────────────────────────────────────────
|
||||||
|
def docker(*args, check=True, capture=True):
|
||||||
|
return subprocess.run(["docker", *args], check=check, capture_output=capture, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
def container_state(name):
|
||||||
|
r = docker("inspect", "-f", "{{.State.Status}}", name, check=False)
|
||||||
|
return r.stdout.strip() if r.returncode == 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
def gpu_flags():
|
||||||
|
flags = ["--device", "/dev/kfd", "--device", "/dev/dri", "--group-add", "video",
|
||||||
|
"--cap-add", "SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--ipc=host"]
|
||||||
|
try:
|
||||||
|
flags += ["--group-add", str(grp.getgrnam("render").gr_gid)] # Arch: /dev/dri/renderD128 is group render
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return flags
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(g, name, preset, envs):
|
||||||
|
cmd = ["docker", "run", "-d", "--name", g["container"], *gpu_flags(),
|
||||||
|
"-p", f'{g["port"]}:8000',
|
||||||
|
"-v", f'{g["models-dir"]}:/models:ro',
|
||||||
|
"-v", f'{g["hf-cache"]}:/root/.cache/huggingface']
|
||||||
|
for k, v in envs.items():
|
||||||
|
cmd += ["-e", f"{k}={v}"]
|
||||||
|
cmd.append(g["image"])
|
||||||
|
cmd += ["--host", "0.0.0.0", "--port", "8000", "--served-model-name", preset.get("served-model-name", name)]
|
||||||
|
cmd += build_args({k: v for k, v in preset.items() if k != "served-model-name"})
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_CAUSE = re.compile(r"hipError|HSA_STATUS|code object|gfx\d+|no kernel image|EngineCore.*(ERROR|Error|error)"
|
||||||
|
r"|^\S*(ERROR|CRITICAL)|RuntimeError|ValueError|NotImplementedError|OutOfMemory|Killed|Segmentation"
|
||||||
|
r"|free memory|not supported|Unsupported|Traceback|memory profiling|kv cache memory|weights take|peak", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def root_cause(lines):
|
||||||
|
"""The lines that explain a dead engine core: known ROCm/vLLM failure signatures, the FIRST
|
||||||
|
traceback's error line, and the last 8 lines — not the parent's 40-line stack trace."""
|
||||||
|
out, seen = [], set()
|
||||||
|
for l in lines:
|
||||||
|
if ROOT_CAUSE.search(l) and "api_server" not in l and "core_client" not in l and "launch_core_engines" not in l:
|
||||||
|
key = l.strip()[:120]
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key); out.append(l.rstrip())
|
||||||
|
if len(out) >= 30:
|
||||||
|
break
|
||||||
|
return out + ["…"] + [l.rstrip() for l in lines[-8:]]
|
||||||
|
|
||||||
|
|
||||||
|
# ─── commands ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
def cmd_up(a, cfg):
|
||||||
|
g = cfg["global"]
|
||||||
|
if a.preset not in cfg or a.preset == "global":
|
||||||
|
sys.exit(f"unknown preset {a.preset!r}; have: {', '.join(k for k in cfg if k != 'global')}")
|
||||||
|
preset = dict(cfg[a.preset])
|
||||||
|
preset.update(dict(kv.split("=", 1) for kv in a.set)) # --set gpu-memory-utilization=0.73 --set max-model-len=8192
|
||||||
|
preset.setdefault("gpu-memory-utilization", g.get("gpu-memory-utilization", "0.75"))
|
||||||
|
envs = {k[4:]: v for k, v in g.items() if k.startswith("env.")}
|
||||||
|
envs.update({k[4:]: v for k, v in preset.items() if k.startswith("env.")})
|
||||||
|
envs.update(dict(e.split("=", 1) for e in a.env))
|
||||||
|
util = float(preset["gpu-memory-utilization"])
|
||||||
|
reserve = float(a.reserve if a.reserve is not None else g.get("reserve-gb", 2.0))
|
||||||
|
drain = float(g.get("drain-gb", 4.0))
|
||||||
|
|
||||||
|
cmd = run_cmd(g, a.preset, preset, envs)
|
||||||
|
print("docker command:\n " + " ".join(shlex.quote(c) for c in cmd) + "\n")
|
||||||
|
if a.dry_run:
|
||||||
|
return
|
||||||
|
|
||||||
|
st = container_state(g["container"])
|
||||||
|
if st == "running":
|
||||||
|
sys.exit(f"container {g['container']} is already running — `vllmctl.py down` first")
|
||||||
|
if st: # leftover from a crash: its log is the evidence, show the tail, then clear the name
|
||||||
|
r = docker("logs", "--tail", "15", g["container"], check=False)
|
||||||
|
print(f"removing old container ({st}); its last lines were:\n" + "\n".join(" | " + l for l in (r.stdout + r.stderr).strip().splitlines()[-15:]))
|
||||||
|
docker("rm", g["container"], check=False)
|
||||||
|
|
||||||
|
# 1. clean card — unload resident router models through its API while it is still up, then drain
|
||||||
|
if router_active():
|
||||||
|
router_unload(g["router"])
|
||||||
|
used = wait_drain(drain, 180, "before start")
|
||||||
|
used, total, gtt = vram()
|
||||||
|
if used is None or total is None:
|
||||||
|
if not a.force:
|
||||||
|
sys.exit(f"ABORT: cannot read {DRM}/mem_info_vram_* — no VRAM guard, not starting blind (--force overrides)")
|
||||||
|
print("⚠ FORCED without a VRAM guard (sysfs unreadable)"); used, total = 0, 1
|
||||||
|
if used > drain * 1e9 and not a.force:
|
||||||
|
sys.exit(f"ABORT: card not clean — {gb(used)} VRAM used (> drain-gb {drain}). Leftover model / GPU apps?")
|
||||||
|
|
||||||
|
# 2. projection — abort here costs nothing, llama.service is still running
|
||||||
|
budget = util * total
|
||||||
|
free_after = total - used - budget
|
||||||
|
weights = float(preset.get("x-weights-gb", 0) or 0) * 1e9
|
||||||
|
print(f"VRAM: total {gb(total)} · used now {gb(used)} (GTT {gb(gtt)}) · vLLM budget {util:.2f} × total = {gb(budget)}"
|
||||||
|
f"\n → projected free {gb(free_after)} (reserve {reserve} GB)")
|
||||||
|
if weights:
|
||||||
|
room = budget - weights
|
||||||
|
print(f" budget − weights {gb(weights)} = {gb(room)} for activations + KV cache"
|
||||||
|
+ (" ⚠ < 1 GB: expect 'no memory for KV cache' → raise util or lower max-model-len" if room < 1e9 else ""))
|
||||||
|
if free_after < reserve * 1e9:
|
||||||
|
suggest = max(0.0, (total - used - reserve * 1e9) / total)
|
||||||
|
msg = (f"projected free {gb(free_after)} < reserve {reserve} GB. Max safe gpu-memory-utilization right now "
|
||||||
|
f"= {suggest:.2f} (desktop is using {gb(used)}).")
|
||||||
|
if not a.force:
|
||||||
|
sys.exit("ABORT: " + msg + f"\n → free the desktop (vramx) or `--set gpu-memory-utilization={suggest:.2f}` "
|
||||||
|
f"(+ `--set max-model-len=8192` if weights leave < 1 GB); --force to start anyway.")
|
||||||
|
print("⚠ FORCED: " + msg)
|
||||||
|
|
||||||
|
# 3. router gate — the only thing that stops a LAN client from loading a model on top of vLLM
|
||||||
|
if router_active():
|
||||||
|
if a.stop_router:
|
||||||
|
print("stopping llama.service (sudo) …")
|
||||||
|
subprocess.run(["sudo", "systemctl", "stop", "llama.service"], check=True)
|
||||||
|
elif a.unsafe_keep_router:
|
||||||
|
print("⚠ llama.service stays ACTIVE: any client request to :11343 while vLLM runs = GTT freeze.")
|
||||||
|
else:
|
||||||
|
sys.exit("ABORT: llama.service is active. Any client request to :11343 would load a model on top "
|
||||||
|
"of vLLM and freeze the PC.\n → re-run with --stop-router (sudo systemctl stop llama.service), "
|
||||||
|
"or --unsafe-keep-router if you really know nothing will call the router.")
|
||||||
|
used2, _, _ = vram()
|
||||||
|
if used2 is not None and used2 > used + 1e9: # a client loaded something between projection and stop
|
||||||
|
print(f"⚠ VRAM grew to {gb(used2)} meanwhile — vLLM's own free-memory check should refuse; watch the start")
|
||||||
|
|
||||||
|
# 4. start + wait
|
||||||
|
cid = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout.strip()
|
||||||
|
print(f"started {cid[:12]} — waiting for http://127.0.0.1:{g['port']}/health (timeout {g.get('health-timeout', 1800)} s; "
|
||||||
|
f"first start compiles kernels)")
|
||||||
|
end = time.time() + int(g.get("health-timeout", 1800))
|
||||||
|
last_log, shown = 0, set()
|
||||||
|
while time.time() < end:
|
||||||
|
st = container_state(g["container"])
|
||||||
|
if st != "running":
|
||||||
|
r = docker("logs", g["container"], check=False)
|
||||||
|
print("root cause candidates (full log: docker logs " + g["container"] + "):")
|
||||||
|
for l in root_cause((r.stdout + r.stderr).splitlines()):
|
||||||
|
print(" | " + l[:200])
|
||||||
|
sys.exit(f"ABORT: container is {st or 'gone'}. If a line names gfx1102 / 'code object' / hipErrorNoBinaryForGpu: "
|
||||||
|
f"uncomment env.HSA_OVERRIDE_GFX_VERSION = 11.0.0 in [global] and retry." + router_hint())
|
||||||
|
try:
|
||||||
|
if http(f"http://127.0.0.1:{g['port']}/health", timeout=3).status == 200:
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if time.time() - last_log > 30:
|
||||||
|
r = docker("logs", "--tail", "3", g["container"], check=False)
|
||||||
|
tail = (r.stdout + r.stderr).strip().splitlines()[-3:]
|
||||||
|
for t in tail:
|
||||||
|
if t not in shown: # only lines not printed before (compile phases are quiet for minutes)
|
||||||
|
print(" | " + t[:160]); shown.add(t)
|
||||||
|
last_log = time.time()
|
||||||
|
time.sleep(3)
|
||||||
|
else:
|
||||||
|
sys.exit("ABORT: no /health within the timeout — `vllmctl.py logs`, then `vllmctl.py down`" + router_hint())
|
||||||
|
used, total, gtt = vram()
|
||||||
|
print(f"UP. VRAM used {gb(used)} / free {gb(total - used)} · GTT {gb(gtt)}")
|
||||||
|
try:
|
||||||
|
ids = [m["id"] for m in json.load(http(f"http://127.0.0.1:{g['port']}/v1/models")).get("data", [])]
|
||||||
|
print(f"models: {', '.join(ids)} → http://192.168.0.204:{g['port']}/v1")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"(/v1/models not readable yet: {e})")
|
||||||
|
print(f"next: ./bench-vs.py -t vllm:{preset.get('served-model-name', a.preset)} · when done: ./vllmctl.py down")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_down(a, cfg):
|
||||||
|
g = cfg["global"]
|
||||||
|
st = container_state(g["container"])
|
||||||
|
if st:
|
||||||
|
print(f"stopping {g['container']} ({st}) …")
|
||||||
|
docker("stop", "-t", "60", g["container"], check=False)
|
||||||
|
docker("rm", g["container"], check=False)
|
||||||
|
else:
|
||||||
|
print("container not running")
|
||||||
|
used = wait_drain(float(g.get("drain-gb", 4.0)), 120, "after stop")
|
||||||
|
used, total, gtt = vram()
|
||||||
|
print(f"VRAM used {gb(used)} / free {gb(total - used) if total and used is not None else '-'} · GTT {gb(gtt)}")
|
||||||
|
if not router_active():
|
||||||
|
print("llama.service is stopped → `sudo systemctl start llama.service` to bring the router back")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_status(a, cfg):
|
||||||
|
g = cfg["global"]
|
||||||
|
used, total, gtt = vram()
|
||||||
|
print(f"container {g['container']}: {container_state(g['container']) or 'not running'}")
|
||||||
|
print(f"VRAM used {gb(used)} / total {gb(total)} · GTT {gb(gtt)}")
|
||||||
|
print(f"llama.service: {'ACTIVE' if router_active() else 'stopped'} · loaded: {router_loaded(g['router']) or '-'}")
|
||||||
|
try:
|
||||||
|
ids = [m["id"] for m in json.load(http(f"http://127.0.0.1:{g['port']}/v1/models", timeout=3)).get("data", [])]
|
||||||
|
print(f"vLLM :{g['port']} serving: {', '.join(ids)}")
|
||||||
|
except Exception:
|
||||||
|
print(f"vLLM :{g['port']}: not answering")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_logs(a, cfg):
|
||||||
|
os.execvp("docker", ["docker", "logs", "-f", "--tail", "100", cfg["global"]["container"]])
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_pull(a, cfg):
|
||||||
|
os.execvp("docker", ["docker", "pull", cfg["global"]["image"]])
|
||||||
|
|
||||||
|
|
||||||
|
GPU_CHECK = r"""
|
||||||
|
import os, torch
|
||||||
|
print('HSA_OVERRIDE_GFX_VERSION =', os.environ.get('HSA_OVERRIDE_GFX_VERSION', '(unset)'))
|
||||||
|
print('cuda available:', torch.cuda.is_available())
|
||||||
|
p = torch.cuda.get_device_properties(0)
|
||||||
|
print('device:', torch.cuda.get_device_name(0) or '(no name)', p.gcnArchName, f'{p.total_memory / 1e9:.1f} GB')
|
||||||
|
print('torch kernel …', end=' ', flush=True) # a hard abort here (no code object for this arch) prints nothing more
|
||||||
|
x = torch.ones(1024, device='cuda'); torch.cuda.synchronize(); print('ok', x.sum().item())
|
||||||
|
import vllm._C # noqa
|
||||||
|
ops = sorted({s.name.split('::', 1)[1] for s in torch._C._jit_get_all_schemas() if s.name.startswith('_C::')})
|
||||||
|
print(f'vllm _C ops registered: {len(ops)}')
|
||||||
|
f16 = dict(device='cuda', dtype=torch.float16)
|
||||||
|
probes = {'silu_and_mul': lambda: torch.ops._C.silu_and_mul(torch.empty(4, 8, **f16), torch.randn(4, 16, **f16)),
|
||||||
|
'gelu_fast': lambda: torch.ops._C.gelu_fast(torch.empty(4, 8, **f16), torch.randn(4, 8, **f16)),
|
||||||
|
'rms_norm': lambda: torch.ops._C.rms_norm(torch.empty(4, 8, **f16), torch.randn(4, 8, **f16), torch.ones(8, **f16), 1e-6)}
|
||||||
|
for name, fn in probes.items():
|
||||||
|
if name in ops:
|
||||||
|
print(f'vllm custom op {name} …', end=' ', flush=True); fn(); torch.cuda.synchronize(); print('ok -> the image has kernels for this arch')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('none of the probe ops exist in this build; first ops:', ops[:15])
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_gpu_check(a, cfg):
|
||||||
|
"""Does the image see the card? Prints torch's device name + arch (needs no model)."""
|
||||||
|
g = cfg["global"]
|
||||||
|
envs = [x for k, v in g.items() if k.startswith("env.") for x in ("-e", f"{k[4:]}={v}")]
|
||||||
|
envs += [x for e in a.env for x in ("-e", e)]
|
||||||
|
code = GPU_CHECK
|
||||||
|
cmd = ["docker", "run", "--rm", *gpu_flags(), *envs, "--entrypoint", "python3", g["image"], "-u", "-c", code]
|
||||||
|
print(" ".join(shlex.quote(c) for c in cmd))
|
||||||
|
os.execvp("docker", cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_exec(a, cfg):
|
||||||
|
g = cfg["global"]
|
||||||
|
if container_state(g["container"]) == "running":
|
||||||
|
os.execvp("docker", ["docker", "exec", "-it", g["container"], *a.cmd])
|
||||||
|
envs = [x for k, v in g.items() if k.startswith("env.") for x in ("-e", f"{k[4:]}={v}")]
|
||||||
|
os.execvp("docker", ["docker", "run", "--rm", "-it", *gpu_flags(), *envs, "--entrypoint", a.cmd[0], g["image"], *a.cmd[1:]])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--ini", default=INI)
|
||||||
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||||
|
up = sub.add_parser("up"); up.add_argument("preset")
|
||||||
|
up.add_argument("--stop-router", action="store_true", help="sudo systemctl stop llama.service first")
|
||||||
|
up.add_argument("--unsafe-keep-router", action="store_true", help="leave llama.service running (freeze risk)")
|
||||||
|
up.add_argument("--force", action="store_true", help="ignore the VRAM projection / clean-card checks")
|
||||||
|
up.add_argument("--reserve", type=float, help="override reserve-gb")
|
||||||
|
up.add_argument("--dry-run", action="store_true", help="print the docker command only")
|
||||||
|
up.add_argument("--env", action="append", default=[], metavar="KEY=VAL", help="extra container env (e.g. HSA_OVERRIDE_GFX_VERSION=11.0.0)")
|
||||||
|
up.add_argument("--set", action="append", default=[], metavar="KEY=VAL", help="override a preset key for this start (e.g. gpu-memory-utilization=0.73)")
|
||||||
|
sub.add_parser("down"); sub.add_parser("status"); sub.add_parser("logs"); sub.add_parser("pull")
|
||||||
|
gc = sub.add_parser("gpu-check"); gc.add_argument("--env", action="append", default=[], metavar="KEY=VAL")
|
||||||
|
ex = sub.add_parser("exec"); ex.add_argument("cmd", nargs=argparse.REMAINDER)
|
||||||
|
a = ap.parse_args()
|
||||||
|
cfg = parse_ini(a.ini)
|
||||||
|
if "global" not in cfg:
|
||||||
|
sys.exit(f"{a.ini}: no [global] section")
|
||||||
|
{"up": cmd_up, "down": cmd_down, "status": cmd_status, "logs": cmd_logs, "pull": cmd_pull,
|
||||||
|
"gpu-check": cmd_gpu_check, "exec": cmd_exec}[a.cmd](a, cfg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -28,7 +28,7 @@ WHISPER_LANG=auto
|
|||||||
|
|
||||||
which expands to:
|
which expands to:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
whisper-server --model $WHISPER_MODEL \
|
whisper-server --model $WHISPER_MODEL \
|
||||||
--host 0.0.0.0 --port $WHISPER_PORT \
|
--host 0.0.0.0 --port $WHISPER_PORT \
|
||||||
--inference-path /v1/audio/transcriptions \
|
--inference-path /v1/audio/transcriptions \
|
||||||
@@ -56,7 +56,7 @@ with auth in front — whisper-server has no `--api-key`.
|
|||||||
`git pull` edit is on disk immediately — but systemd caches unit files, so it still needs a reload.
|
`git pull` edit is on disk immediately — but systemd caches unit files, so it still needs a reload.
|
||||||
(Copying into `/etc` instead would work, but then every repo edit silently drifts.) On the server:
|
(Copying into `/etc` instead would work, but then every repo edit silently drifts.) On the server:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
dotsync # nudges you if this dir changed
|
dotsync # nudges you if this dir changed
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl restart whisper@turbo whisper@small
|
sudo systemctl restart whisper@turbo whisper@small
|
||||||
@@ -66,7 +66,7 @@ Editing only an `.env` needs no `daemon-reload` — just restart that instance.
|
|||||||
|
|
||||||
First install (stow does not install units):
|
First install (stow does not install units):
|
||||||
|
|
||||||
```
|
```sh
|
||||||
yay -S whisper-cpp ffmpeg
|
yay -S whisper-cpp ffmpeg
|
||||||
curl -L -o ~/software/models/ggml-large-v3-turbo-q5_0.bin \
|
curl -L -o ~/software/models/ggml-large-v3-turbo-q5_0.bin \
|
||||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin
|
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin
|
||||||
@@ -99,7 +99,7 @@ transcribing it, and the `gemma-4-E4B` preset samples at temp 1.0. Whisper is pu
|
|||||||
|
|
||||||
**Still available as a fallback** for wav/mp3/flac (no config change needed), if these services are down:
|
**Still available as a fallback** for wav/mp3/flac (no config change needed), if these services are down:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
curl -F file=@clip.wav -F model=gemma-4-E4B-it-UD-Q8_K_XL \
|
curl -F file=@clip.wav -F model=gemma-4-E4B-it-UD-Q8_K_XL \
|
||||||
http://$SERVER:11343/v1/audio/transcriptions
|
http://$SERVER:11343/v1/audio/transcriptions
|
||||||
```
|
```
|
||||||
@@ -122,7 +122,7 @@ with line breaks mid-sentence.
|
|||||||
|
|
||||||
## Verifying
|
## Verifying
|
||||||
|
|
||||||
```
|
```sh
|
||||||
systemctl status whisper@turbo whisper@small
|
systemctl status whisper@turbo whisper@small
|
||||||
rocm-smi --showmeminfo vram # must be UNCHANGED vs before the services started
|
rocm-smi --showmeminfo vram # must be UNCHANGED vs before the services started
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ have no server integration, so **model size is the only real dial**.
|
|||||||
|
|
||||||
A/B any candidate without touching a service:
|
A/B any candidate without touching a service:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
curl -LO https://github.com/ggml-org/whisper.cpp/raw/master/samples/jfk.wav # known text
|
curl -LO https://github.com/ggml-org/whisper.cpp/raw/master/samples/jfk.wav # known text
|
||||||
time whisper-cli -m ~/software/models/ggml-small.en-q5_1.bin -f jfk.wav -t 6 -nt
|
time whisper-cli -m ~/software/models/ggml-small.en-q5_1.bin -f jfk.wav -t 6 -nt
|
||||||
time whisper-cli -m ~/software/models/ggml-base.en-q5_1.bin -f jfk.wav -t 6 -nt
|
time whisper-cli -m ~/software/models/ggml-base.en-q5_1.bin -f jfk.wav -t 6 -nt
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Synthwave '84 — matches ../kitty/theme.conf
|
# Colours from Robb Owen's SynthWave '84 palette, arranged for this repo (yellow and magenta
|
||||||
|
# swapped). Background/foreground match kitty.conf's own colours.
|
||||||
|
|
||||||
[colors.primary]
|
[colors.primary]
|
||||||
background = "#262335"
|
background = "#262335"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
user-db:hyde_hyprland
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# gui package — put HyDE's script dir on the systemd *user manager* PATH, so
|
||||||
|
# user units can resolve bare hyde script names. Nothing else on the system ever
|
||||||
|
# adds it: HyDE only self-bootstraps PATH inside its own wrappers
|
||||||
|
# (hyde-shell/app.sh), so anything a unit execs bare is "command not found" —
|
||||||
|
# hypridle's lock_cmd was the incident (TROUBLESHOOTING.md symptom 8).
|
||||||
|
# Lifecycle: the environment generator reads this at user-manager start, but the
|
||||||
|
# session-start PATH import overwrites the manager's PATH afterwards — daemon-reload
|
||||||
|
# won't re-apply this over an imported PATH; only relogin does. The fish twin
|
||||||
|
# gui/.config/fish/conf.d/hyde-path.fish is what makes that import *carry* the dir
|
||||||
|
# (it feeds the session PATH the import copies from), so both files are needed and
|
||||||
|
# together they hold in either order.
|
||||||
|
PATH=$HOME/.local/lib/hyde:$PATH
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
## gui package — pin which config Hyprland starts with.
|
## gui package — pin which config Hyprland starts with.
|
||||||
##
|
##
|
||||||
## HyDE 26.x configures Hyprland in Lua and points HYPRLAND_CONFIG at
|
## Points HYPRLAND_CONFIG at HyDE's Lua entry point, explicitly. The Lua chain
|
||||||
## ~/.local/share/hypr/hyde.lua. Once that is set, Hyprland never reads
|
## loads HyDE's lua/ modules and then the user override
|
||||||
## ~/.config/hypr/hyprland.conf, so the entire dots hypr layer (theme, keybinds,
|
## ~/.config/hypr/hyprland.lua (repo: gui/.config/hypr/hyprland.lua, which pulls
|
||||||
## window rules, monitors, userprefs) is bypassed and the session comes up as
|
## in the per-host host.lua). None of the old hyprlang .conf files are read in
|
||||||
## stock HyDE — silently, with `hyprctl configerrors` empty.
|
## Lua mode. See LUA-PORT.md.
|
||||||
##
|
##
|
||||||
## Why it is set from *fish* of all places: the update dropped a real
|
## Why it is set from *fish* of all places: HyDE's untracked
|
||||||
## ~/.config/fish/conf.d/hyde.fish (untracked, HyDE-owned) which exports it, and
|
## ~/.config/fish/conf.d/hyde.fish exports it, and fish sources every
|
||||||
## fish sources every conf.d/*.fish at startup. SDDM launches the session through
|
## conf.d/*.fish at startup. SDDM launches the session through the login shell
|
||||||
## the login shell (/usr/bin/start-hyprland, the `hyprland.desktop` entry — not
|
## (/usr/bin/start-hyprland, the `hyprland.desktop` entry — not
|
||||||
## `hyprland-uwsm.desktop`), so the compositor inherits whatever fish exported.
|
## `hyprland-uwsm.desktop`), so the compositor inherits whatever fish exported.
|
||||||
## HyDE's snippet only assigns `if test -z "$HYPRLAND_CONFIG"`, and conf.d files
|
## HyDE's snippet only assigns `if test -z "$HYPRLAND_CONFIG"`, and conf.d files
|
||||||
## load in alphabetical order, so this file's `00-` prefix means ours is already
|
## load in alphabetical order, so this file's `00-` prefix means ours is already
|
||||||
## set by the time hyde.fish looks — do not rename it above `hyde.fish`.
|
## set by the time hyde.fish looks — do not rename it above `hyde.fish`.
|
||||||
##
|
##
|
||||||
## The uwsm twin of this lives in gui/.config/uwsm/env-hyprland.d/10-dots.sh and
|
## The uwsm twin lives in gui/.config/uwsm/env-hyprland.d/10-dots.sh and covers
|
||||||
## covers the `hyprland-uwsm.desktop` session entry, which sources uwsm env files
|
## the `hyprland-uwsm.desktop` session entry. Both are needed; neither is
|
||||||
## instead of the shell. Both are needed; neither is sufficient alone.
|
## sufficient alone. Keeping the pins alive (instead of trusting HyDE's default)
|
||||||
|
## preserves the invariant that the dots repo decides the config, and makes any
|
||||||
|
## rollback a one-line edit. Takes effect at session start — relogin, not
|
||||||
|
## `hyprctl reload`.
|
||||||
##
|
##
|
||||||
## STOPGAP: this pins ~/.local/share/hyde/hyprland.conf, which current HyDE no
|
## ROLLBACK to hyprlang (the chain was retired at LUA-PORT.md step 8,
|
||||||
## longer ships (ours is a leftover of the pre-update install). The supported
|
## 2026-09-10): restore gui/.config/hypr, wm/.config/hypr, lw/.config/hypr and
|
||||||
## path is porting the hypr layer to ~/.config/hypr/hyprland.lua. See
|
## gui/.local/share/hyde from bddbbf3 (the last pre-step-8 commit), ./install.sh, point both
|
||||||
## HYDE-UPDATE.md. Takes effect at session start — relogin, not `hyprctl reload`.
|
## pins back at "$HOME/.config/hypr/hyprland.conf" and relogin. d851213 is the
|
||||||
set -gx HYPRLAND_CONFIG "$HOME/.config/hypr/hyprland.conf"
|
## last hyprlang `main` (the port merged 2026-11-09).
|
||||||
|
set -l hypr_data "$XDG_DATA_HOME"
|
||||||
|
test -n "$hypr_data"; or set hypr_data "$HOME/.local/share"
|
||||||
|
set -gx HYPRLAND_CONFIG "$hypr_data/hypr/hyde.lua"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
|
"diagnostics": {
|
||||||
|
"globals": ["hl", "hyde", "check_require"]
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"library": ["/usr/share/hypr/stubs", "~/.local/share/hypr/lua"]
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"version": "Lua 5.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# See https://wiki.hypr.land/Configuring/Animations/
|
|
||||||
# Edit or add presets in ./animations/, then run 'animations.sh --select' to switch.
|
|
||||||
# (noanim layerrules for hyprpicker/selection live in userprefs.conf)
|
|
||||||
|
|
||||||
$ANIMATION=theme
|
|
||||||
# Absolute, not ./ — HyDE re-sources $ANIMATION_PATH from ~/.local/share/hyde/hyprland.conf,
|
|
||||||
# where a relative path resolves against that directory instead of this one.
|
|
||||||
$ANIMATION_PATH=~/.config/hypr/animations/theme.conf
|
|
||||||
source = $ANIMATION_PATH
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Classic"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
|
|
||||||
animation = windows, 1, 7, myBezier
|
|
||||||
animation = windowsOut, 1, 7, default, popin 80%
|
|
||||||
animation = border, 1, 10, default
|
|
||||||
animation = borderangle, 1, 8, default
|
|
||||||
animation = fade, 1, 7, default
|
|
||||||
animation = workspaces, 1, 6, default
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Diablo-1"
|
|
||||||
# credit https://github.com/Itz-Abhishek-Tiwari
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = 1
|
|
||||||
bezier = default, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = overshot, 0.13, 0.99, 0.29, 1.08
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
bezier = bounce, 0.4, 0.9, 0.6, 1.0
|
|
||||||
bezier = snappyReturn, 0.4, 0.9, 0.6, 1.0
|
|
||||||
|
|
||||||
bezier = slideInFromRight, 0.5, 0.0, 0.5, 1.0
|
|
||||||
animation = windows, 1, 5, snappyReturn, slidevert
|
|
||||||
animation = windowsIn, 1, 5, snappyReturn, slidevert right
|
|
||||||
|
|
||||||
animation = windowsOut, 1, 5, snappyReturn, slide
|
|
||||||
animation = windowsMove, 1, 6, bounce, slide
|
|
||||||
animation = layersOut, 1, 5, bounce, slidevert right
|
|
||||||
animation = fadeIn, 1, 10, default
|
|
||||||
animation = fadeOut, 1, 10, default
|
|
||||||
animation = fadeSwitch, 1, 10, default
|
|
||||||
animation = fadeShadow, 1, 10, default
|
|
||||||
animation = fadeDim, 1, 10, default
|
|
||||||
animation = fadeLayers, 1, 10, default
|
|
||||||
animation = workspaces, 1, 7, overshot, slidevert
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = layers, 1, 4, bounce, slidevert right
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Diablo-2"
|
|
||||||
# credit https://github.com/Itz-Abhishek-Tiwari
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = 1
|
|
||||||
bezier = default, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = overshot, 0.13, 0.99, 0.29, 1.08
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
animation = windows, 1, 7, wind, popin
|
|
||||||
animation = windowsIn, 1, 7, overshot, popin
|
|
||||||
animation = windowsOut, 1, 5, overshot, popin
|
|
||||||
animation = windowsMove, 1, 6, overshot, slide
|
|
||||||
animation = layers, 1, 5, default, popin
|
|
||||||
animation = fadeIn, 1, 10, default
|
|
||||||
animation = fadeOut, 1, 10, default
|
|
||||||
animation = fadeSwitch, 1, 10, default
|
|
||||||
animation = fadeShadow, 1, 10, default
|
|
||||||
animation = fadeDim, 1, 10, default
|
|
||||||
animation = fadeLayers, 1, 10, default
|
|
||||||
animation = workspaces, 1, 7, overshot, slidevert
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
# // ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# // █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Animations/
|
|
||||||
# credits: https://github.com/prasanthrangan/hyprdots
|
|
||||||
|
|
||||||
animations:enabled=false
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Dynamic"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
animation = fade, 1, 10, default
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "End4"
|
|
||||||
# credit https://github.com/end-4/dots-hyprland
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
# Animation curves
|
|
||||||
|
|
||||||
bezier = linear, 0, 0, 1, 1
|
|
||||||
bezier = md3_standard, 0.2, 0, 0, 1
|
|
||||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
|
||||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
|
||||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
|
||||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
|
||||||
bezier = menu_decel, 0.1, 1, 0, 1
|
|
||||||
bezier = menu_accel, 0.38, 0.04, 1, 0.07
|
|
||||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
|
||||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
|
||||||
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
|
|
||||||
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
|
|
||||||
# Animation configs
|
|
||||||
animation = windows, 1, 3, md3_decel, popin 60%
|
|
||||||
animation = windowsIn, 1, 3, md3_decel, popin 60%
|
|
||||||
animation = windowsOut, 1, 3, md3_accel, popin 60%
|
|
||||||
animation = border, 1, 10, default
|
|
||||||
animation = fade, 1, 3, md3_decel
|
|
||||||
# animation = layers, 1, 2, md3_decel, slide
|
|
||||||
animation = layersIn, 1, 3, menu_decel, slide
|
|
||||||
animation = layersOut, 1, 1.6, menu_accel
|
|
||||||
animation = fadeLayersIn, 1, 2, menu_decel
|
|
||||||
animation = fadeLayersOut, 1, 4.5, menu_accel
|
|
||||||
animation = workspaces, 1, 7, menu_decel, slide
|
|
||||||
# animation = workspaces, 1, 2.5, softAcDecel, slide
|
|
||||||
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
|
|
||||||
# animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Fast"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = linear, 0, 0, 1, 1
|
|
||||||
bezier = md3_standard, 0.2, 0, 0, 1
|
|
||||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
|
||||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
|
||||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
|
||||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
|
||||||
bezier = fluent_decel, 0.1, 1, 0, 1
|
|
||||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
|
||||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
|
||||||
animation = windows, 1, 3, md3_decel, popin 60%
|
|
||||||
animation = border, 1, 10, default
|
|
||||||
animation = fade, 1, 2.5, md3_decel
|
|
||||||
animation = workspaces, 1, 3.5, easeOutExpo, slide
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "High"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
animation = fade, 1, 10, default
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Ja"
|
|
||||||
# credit https://github.com/JaKooLit/Hyprland-Dots
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = yes
|
|
||||||
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
|
||||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
|
||||||
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 5, winIn, slide
|
|
||||||
animation = windowsOut, 1, 3, smoothOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
#animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
|
|
||||||
animation = fade, 1, 3, smoothOut
|
|
||||||
animation = workspaces, 1, 5, overshot
|
|
||||||
|
|
||||||
# animations for -git or version >0.42.0
|
|
||||||
animation = workspacesIn, 1, 5, winIn, slide
|
|
||||||
animation = workspacesOut, 1, 5, winOut, slide
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Me-1"
|
|
||||||
# credit https://github.com/mahaveergurjar
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
# Animation curves
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
# bezier = linear, 0, 0, 1, 1
|
|
||||||
bezier = md3_standard, 0.2, 0, 0, 1
|
|
||||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
|
||||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
|
||||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
|
||||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
|
||||||
bezier = menu_decel, 0.1, 1, 0, 1
|
|
||||||
bezier = menu_accel, 0.38, 0.04, 1, 0.07
|
|
||||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
|
||||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
|
||||||
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
|
|
||||||
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
|
|
||||||
|
|
||||||
# Animation configs
|
|
||||||
# animation = windows, 1, 3, md3_decel, popin 60%
|
|
||||||
# animation = windowsIn, 1, 3, md3_decel, popin 60%
|
|
||||||
# animation = windowsOut, 1, 3, md3_accel, popin 60%
|
|
||||||
# animation = windows, 1, 6, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
|
|
||||||
# animation = border, 1, 10, default
|
|
||||||
animation = fade, 1, 3, md3_decel
|
|
||||||
# animation = layers, 1, 2, md3_decel, slide
|
|
||||||
animation = layersIn, 1, 3, menu_decel, slide
|
|
||||||
animation = layersOut, 1, 1.6, menu_accel
|
|
||||||
animation = fadeLayersIn, 1, 2, menu_decel
|
|
||||||
animation = fadeLayersOut, 1, 4.5, menu_accel
|
|
||||||
animation = workspaces, 1, 7, menu_decel, slide
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
# animation = workspaces, 1, 2.5, softAcDecel, slide
|
|
||||||
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Me-2"
|
|
||||||
# credit https://github.com/mahaveergurjar
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
# Animation curves
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
bezier = md3_standard, 0.2, 0, 0, 1
|
|
||||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
|
||||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
|
||||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
|
||||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
|
||||||
bezier = menu_decel, 0.1, 1, 0, 1
|
|
||||||
bezier = menu_accel, 0.38, 0.04, 1, 0.07
|
|
||||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
|
||||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
|
||||||
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
|
|
||||||
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
|
|
||||||
|
|
||||||
bezier = OutBack, 0.34, 1.56, 0.64, 1
|
|
||||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
|
||||||
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
# animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
# animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windows, 1, 5, easeInOutCirc
|
|
||||||
animation = windowsOut, 1, 5, OutBack
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = fade, 1, 3, md3_decel
|
|
||||||
animation = layersIn, 1, 3, menu_decel, slide
|
|
||||||
animation = layersOut, 1, 1.6, menu_accel
|
|
||||||
animation = fadeLayersIn, 1, 2, menu_decel
|
|
||||||
animation = fadeLayersOut, 1, 4.5, menu_accel
|
|
||||||
animation = workspaces, 1, 7, menu_decel, slide
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
|
|
||||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Minimal-1"
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
# █▄▄ █▀▀ ▀█ █ █▀▀ █▀█ █▀▀ █░█ █▀█ █░█ █▀▀
|
|
||||||
# █▄█ ██▄ █▄ █ ██▄ █▀▄ █▄▄ █▄█ █▀▄ ▀▄▀ ██▄
|
|
||||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
|
||||||
bezier = winOut, 0.3, -0.3, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
|
|
||||||
|
|
||||||
#▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
#█▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
animation = windows, 1, 6, wind, slide
|
|
||||||
animation = windowsIn, 1, 6, winIn, slide
|
|
||||||
animation = windowsOut, 1, 5, winOut, slide
|
|
||||||
animation = windowsMove, 1, 5, wind, slide
|
|
||||||
animation = border, 1, 1, liner
|
|
||||||
animation = borderangle, 1, 30, liner, loop
|
|
||||||
animation = fade, 1, 10, default
|
|
||||||
animation = workspaces, 1, 5, wind
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Minimal-2"
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = yes
|
|
||||||
|
|
||||||
bezier = quart, 0.25, 1, 0.5, 1
|
|
||||||
|
|
||||||
animation = windows, 1, 6, quart, slide
|
|
||||||
animation = border, 1, 6, quart
|
|
||||||
animation = borderangle, 1, 6, quart
|
|
||||||
animation = fade, 1, 6, quart
|
|
||||||
animation = workspaces, 1, 6, quart
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Moving"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
|
||||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
|
||||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
|
||||||
animation = windows, 1, 5, overshot, slide
|
|
||||||
animation = windowsOut, 1, 3, smoothOut
|
|
||||||
animation = windowsIn, 1, 3, smoothOut
|
|
||||||
animation = windowsMove, 1, 4, smoothIn, slide
|
|
||||||
animation = border, 1, 5, default
|
|
||||||
animation = fade, 1, 5, smoothIn
|
|
||||||
animation = fadeDim, 1, 5, smoothIn
|
|
||||||
animation = workspaces, 1, 6, default
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Optimized"
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = wind, 0.05, 0.85, 0.03, 0.97
|
|
||||||
bezier = winIn, 0.07, 0.88, 0.04, 0.99
|
|
||||||
bezier = winOut, 0.20, -0.15, 0, 1
|
|
||||||
bezier = liner, 1, 1, 1, 1
|
|
||||||
bezier = md3_standard, 0.12, 0, 0, 1
|
|
||||||
bezier = md3_decel, 0.05, 0.80, 0.10, 0.97
|
|
||||||
bezier = md3_accel, 0.20, 0, 0.80, 0.08
|
|
||||||
bezier = overshot, 0.05, 0.85, 0.07, 1.04
|
|
||||||
bezier = crazyshot, 0.1, 1.22, 0.68, 0.98
|
|
||||||
bezier = hyprnostretch, 0.05, 0.82, 0.03, 0.94
|
|
||||||
bezier = menu_decel, 0.05, 0.82, 0, 1
|
|
||||||
bezier = menu_accel, 0.20, 0, 0.82, 0.10
|
|
||||||
bezier = easeInOutCirc, 0.75, 0, 0.15, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.48, 0.38, 1
|
|
||||||
bezier = easeOutExpo, 0.10, 0.94, 0.23, 0.98
|
|
||||||
bezier = softAcDecel, 0.20, 0.20, 0.15, 1
|
|
||||||
bezier = md2, 0.30, 0, 0.15, 1
|
|
||||||
|
|
||||||
bezier = OutBack, 0.28, 1.40, 0.58, 1
|
|
||||||
bezier = easeInOutCirc, 0.78, 0, 0.15, 1
|
|
||||||
|
|
||||||
animation = border, 1, 1.6, liner
|
|
||||||
animation = borderangle, 1, 82, liner, loop
|
|
||||||
animation = windowsIn, 1, 3.2, winIn, slide
|
|
||||||
animation = windowsOut, 1, 2.8, easeOutCirc
|
|
||||||
animation = windowsMove, 1, 3.0, wind, slide
|
|
||||||
animation = fade, 1, 1.8, md3_decel
|
|
||||||
animation = layersIn, 1, 1.8, menu_decel, slide
|
|
||||||
animation = layersOut, 1, 1.5, menu_accel
|
|
||||||
animation = fadeLayersIn, 1, 1.6, menu_decel
|
|
||||||
animation = fadeLayersOut, 1, 1.8, menu_accel
|
|
||||||
animation = workspaces, 1, 4.0, menu_decel, slide
|
|
||||||
animation = specialWorkspace, 1, 2.3, md3_decel, slidefadevert 15%
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Standard"
|
|
||||||
# credit https://github.com/mylinuxforwork/dotfiles
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
enabled = true
|
|
||||||
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
|
|
||||||
animation = windows, 1, 7, myBezier
|
|
||||||
animation = windowsOut, 1, 7, default, popin 80%
|
|
||||||
animation = border, 1, 10, default
|
|
||||||
animation = borderangle, 1, 8, default
|
|
||||||
animation = fade, 1, 7, default
|
|
||||||
animation = workspaces, 1, 6, default
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# -----------------------------------------------------
|
|
||||||
# ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
|
||||||
# █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
|
||||||
#
|
|
||||||
# name "Vertical"
|
|
||||||
# -----------------------------------------------------
|
|
||||||
|
|
||||||
animations {
|
|
||||||
bezier = fluent_decel, 0, 0.2, 0.4, 1
|
|
||||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
|
||||||
bezier = easeOutCubic, 0.33, 1, 0.68, 1
|
|
||||||
bezier = easeinoutsine, 0.37, 0, 0.63, 1
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
animation = windowsIn, 1, 1.5, easeinoutsine, popin 60% # window open
|
|
||||||
animation = windowsOut, 1, 1.5, easeOutCubic, popin 60% # window close.
|
|
||||||
animation = windowsMove, 1, 1.5, easeinoutsine, slide # everything in between, moving, dragging, resizing.
|
|
||||||
|
|
||||||
# Fading
|
|
||||||
animation = fade, 1, 2.5, fluent_decel
|
|
||||||
|
|
||||||
animation = fadeLayersIn, 0
|
|
||||||
animation = border, 0
|
|
||||||
|
|
||||||
|
|
||||||
# Layers
|
|
||||||
animation = layers, 1, 1.5, easeinoutsine, popin
|
|
||||||
|
|
||||||
# Workspaces
|
|
||||||
#animation = workspaces, 1, 3, fluent_decel, slidefade 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
|
|
||||||
animation = workspaces, 1, 3, fluent_decel, slidefadevert 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
|
|
||||||
|
|
||||||
animation = specialWorkspace, 1, 2, fluent_decel, slidefade 10%
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
-- gui package — the dots keybinds, split out of ./hyprland.lua. Required there
|
||||||
|
-- after the app/start overrides (the launchers read hc.app.*) and before
|
||||||
|
-- ./host.lua.
|
||||||
|
|
||||||
|
local hc = hyde.config
|
||||||
|
local MOD = hc.modifiers.main
|
||||||
|
local home = os.getenv("HOME") or ""
|
||||||
|
|
||||||
|
-- ── Keybinds ─────────────────────────────────────────────────────────────────
|
||||||
|
-- Policy: preserve what the dots keybindings.conf did live until the port —
|
||||||
|
-- where a dots bind and a HyDE bind share a combo, the dots action wins and
|
||||||
|
-- the displaced HyDE action is noted. Binds identical in both are not
|
||||||
|
-- repeated here (volume/media/brightness, screenshots, Super+Q/W,
|
||||||
|
-- rofi menus, Super+L lock, Super+Shift+F pin, Super+S scratchpad family,
|
||||||
|
-- Super+Z/X + mouse drag/resize, Super+J togglesplit, Super+T/E/B via the
|
||||||
|
-- app.* overrides in hyprland.lua, Super+Alt+G gamemode, Ctrl+Shift+Esc
|
||||||
|
-- sysmon).
|
||||||
|
|
||||||
|
-- Removes every live bind on a combo regardless of flags. hl.bind's dedup only
|
||||||
|
-- replaces a bind whose flags match exactly; HyDE binds with locked/transparent/
|
||||||
|
-- repeating flags would otherwise stay live next to ours and double-fire.
|
||||||
|
local function unbind_all(combo)
|
||||||
|
-- _active/canonicalize are HyDE-private; if an update renames them, degrade
|
||||||
|
-- to "the displaced HyDE binds double-fire" instead of erroring here and
|
||||||
|
-- silently dropping every bind registered below this line.
|
||||||
|
if not (hyde.binds and hyde.binds._active and hyde.binds.canonicalize) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local canon = hyde.binds.canonicalize(combo)
|
||||||
|
for id, registered in pairs(hyde.binds._active) do
|
||||||
|
if id:sub(1, #canon + 1) == canon .. "|" then
|
||||||
|
hl.unbind(registered)
|
||||||
|
hyde.binds._active[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local _F
|
||||||
|
|
||||||
|
-- Launchers (dots-only combos, no conflict)
|
||||||
|
_F = { description = "[Launcher|Apps] work session (kitty c2)" }
|
||||||
|
hl.bind(MOD .. " + CTRL + I", hl.dsp.exec_cmd("kitty --session " .. home .. "/.config/kitty/c2.conf"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] work session (kitty runners)" }
|
||||||
|
hl.bind(MOD .. " + CTRL + O", hl.dsp.exec_cmd("kitty --session " .. home .. "/.config/kitty/runners.conf"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] pyradio (kitty music)" }
|
||||||
|
hl.bind(MOD .. " + R", hl.dsp.exec_cmd("kitty --session " .. home .. "/.config/kitty/music.conf"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] ferdium" }
|
||||||
|
hl.bind(MOD .. " + Y", hl.dsp.exec_cmd("ferdium"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] betterbird" }
|
||||||
|
hl.bind(MOD .. " + M", hl.dsp.exec_cmd("betterbird"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] freetube" }
|
||||||
|
hl.bind(MOD .. " + F", hl.dsp.exec_cmd("freetube"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] web browser (librewolf)" }
|
||||||
|
hl.bind(MOD .. " + SHIFT + B", hl.dsp.exec_cmd("librewolf"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] terminal emulator (st)" }
|
||||||
|
hl.bind(MOD .. " + CTRL + T", hl.dsp.exec_cmd("st"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] terminal emulator" }
|
||||||
|
hl.bind("CTRL + ALT + T", hl.dsp.exec_cmd(hc.app.terminal), _F)
|
||||||
|
|
||||||
|
-- Rebound fresh so the hc.app.* overrides actually apply: HyDE's binds
|
||||||
|
-- evaluated _apps.browser/_apps.explorer when key_binds.lua ran, baking in the
|
||||||
|
-- hyde-shell open wrappers.
|
||||||
|
_F = { description = "[Launcher|Apps] web browser" }
|
||||||
|
hl.bind(MOD .. " + B", hl.dsp.exec_cmd("zen-browser"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] file explorer" }
|
||||||
|
hl.bind(MOD .. " + E", hl.dsp.exec_cmd("dolphin"), _F)
|
||||||
|
|
||||||
|
-- Conflict resolutions (dots action wins; displaced HyDE action noted)
|
||||||
|
_F = { description = "[Launcher|Apps] web browser (firefox)" } -- displaces HyDE: hide waybar
|
||||||
|
hl.bind(MOD .. " + CTRL + B", hl.dsp.exec_cmd("firefox"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] xmpp (gajim)" } -- displaces HyDE: toggle group (dots keeps it on Super+Shift+G)
|
||||||
|
hl.bind(MOD .. " + G", hl.dsp.exec_cmd("gajim"), _F)
|
||||||
|
_F = { description = "[Window Management] toggle group" } -- displaces HyDE: game launcher
|
||||||
|
hl.bind(MOD .. " + SHIFT + G", hl.dsp.group.toggle(), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] terminal emulator (alacritty)" } -- displaces HyDE: theme select menu
|
||||||
|
hl.bind(MOD .. " + SHIFT + T", hl.dsp.exec_cmd("alacritty"), _F)
|
||||||
|
-- Through `wbar`, never killall: under supervision a killed waybar is back in
|
||||||
|
-- seconds, and a raw relaunch next to it means two bars.
|
||||||
|
_F = { description = "[Theming and Wallpaper] reload hyprland + waybar" } -- displaces HyDE: wallbash mode menu
|
||||||
|
hl.bind(MOD .. " + SHIFT + R", hl.dsp.exec_cmd("hyprctl reload; " .. home .. "/.local/bin/wbar restart"), _F)
|
||||||
|
_F = { description = "[Launcher|Apps] neovim" } -- displaces HyDE: code editor
|
||||||
|
hl.bind(MOD .. " + C", hl.dsp.exec_cmd("kitty nvim"), _F)
|
||||||
|
|
||||||
|
-- Super+K keepassxc — HyDE's keyboard-layout switch carries locked=true, so a
|
||||||
|
-- plain rebind would leave both live (flags are part of a bind's identity).
|
||||||
|
unbind_all(MOD .. " + K") -- displaces HyDE: keyboard layout switch (locked)
|
||||||
|
_F = { description = "[Launcher|Apps] password manager" }
|
||||||
|
hl.bind(MOD .. " + K", hl.dsp.exec_cmd("keepassxc"), _F)
|
||||||
|
|
||||||
|
-- Alt+Tab focus-down — HyDE ships a 4-bind alt-tab switcher (transparent/
|
||||||
|
-- release flags); remove the whole cluster, half a switcher is worse than none.
|
||||||
|
unbind_all("ALT + TAB") -- displaces HyDE: alt-tab switcher (cycle next)
|
||||||
|
unbind_all("ALT + SHIFT + TAB")
|
||||||
|
unbind_all("ALT + ALT_L")
|
||||||
|
unbind_all("ALT + ALT_R")
|
||||||
|
_F = { description = "[Window Management|Change focus] focus down" }
|
||||||
|
hl.bind("ALT + TAB", hl.dsp.focus({ direction = "down" }), _F)
|
||||||
|
|
||||||
|
-- Waybar toggle (custom layout; the legacy Ctrl+Alt+W). systemctl stop is a
|
||||||
|
-- deliberate stop, so Restart=always does not undo it.
|
||||||
|
_F = { description = "[Window Management] toggle waybar" }
|
||||||
|
hl.bind("CTRL + ALT + W", hl.dsp.exec_cmd(home .. "/.local/bin/wbar toggle"), _F)
|
||||||
|
|
||||||
|
-- Fullscreen on Alt+Return (Shift+F11 stays HyDE's cycle-fullscreen).
|
||||||
|
-- Native dispatcher, not exec "hyprctl dispatch fullscreen": in Lua mode that
|
||||||
|
-- CLI arg is evaluated as hl.dispatch(fullscreen) — a nil global, dead bind.
|
||||||
|
_F = { description = "[Window Management] toggle fullscreen" }
|
||||||
|
hl.bind("ALT + RETURN", hl.dsp.window.fullscreen(), _F)
|
||||||
|
|
||||||
|
-- No Super+Delete: HyDE's exit() ends the session on the spot, no confirm, and
|
||||||
|
-- the combo is one slipped finger from Super+Alt+Delete. Ctrl+Alt+Delete (the
|
||||||
|
-- logout menu) stays the way out.
|
||||||
|
unbind_all(MOD .. " + Delete") -- displaces HyDE: exit hyprland session
|
||||||
|
|
||||||
|
-- ── Focus (arrows + hjkl) ────────────────────────────────────────────────────
|
||||||
|
-- Super+Ctrl+Up/Down: movefocus — displaces HyDE: nearest-empty (Down), which
|
||||||
|
-- moves to Super+Ctrl+E below. Super+Ctrl+Left/Right move the window to r±1
|
||||||
|
-- (Workspaces below); r±1 nav is on Super+Left/Right and Super+Ctrl+Shift.
|
||||||
|
for dir, key in pairs({ left = "LEFT", right = "RIGHT", up = "UP", down = "DOWN" }) do
|
||||||
|
_F = { description = "[Window Management|Change focus] focus " .. dir }
|
||||||
|
if key == "UP" or key == "DOWN" then
|
||||||
|
hl.bind(MOD .. " + CTRL + " .. key, hl.dsp.focus({ direction = dir }), _F)
|
||||||
|
end
|
||||||
|
hl.bind("ALT + CTRL + " .. key, hl.dsp.focus({ direction = dir }), _F)
|
||||||
|
end
|
||||||
|
for dir, key in pairs({ left = "H", right = "L", up = "K", down = "J" }) do
|
||||||
|
_F = { description = "[Window Management|Change focus] focus " .. dir }
|
||||||
|
hl.bind("ALT + CTRL + " .. key, hl.dsp.focus({ direction = dir }), _F)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Super+Ctrl+H/L carried a deliberate double action in the hyprlang chain:
|
||||||
|
-- changegroupactive (a no-op outside a group) AND movefocus fired together.
|
||||||
|
-- Lua dedup would collapse the pair, so one function does both.
|
||||||
|
-- (Replaces HyDE's plain group-nav binds on the same combos.) Unlike the
|
||||||
|
-- arrows, they do not move the window to r±1 — that is Super+Ctrl+Alt+H/L.
|
||||||
|
_F = { description = "[Window Management|Change focus] group prev + focus left" }
|
||||||
|
hl.bind(MOD .. " + CTRL + H", function()
|
||||||
|
hl.dispatch(hl.dsp.group.prev())
|
||||||
|
hl.dispatch(hl.dsp.focus({ direction = "left" }))
|
||||||
|
end, _F)
|
||||||
|
_F = { description = "[Window Management|Change focus] group next + focus right" }
|
||||||
|
hl.bind(MOD .. " + CTRL + L", function()
|
||||||
|
hl.dispatch(hl.dsp.group.next())
|
||||||
|
hl.dispatch(hl.dsp.focus({ direction = "right" }))
|
||||||
|
end, _F)
|
||||||
|
_F = { description = "[Window Management|Change focus] focus up" }
|
||||||
|
hl.bind(MOD .. " + CTRL + K", hl.dsp.focus({ direction = "up" }), _F)
|
||||||
|
_F = { description = "[Window Management|Change focus] focus down" }
|
||||||
|
hl.bind(MOD .. " + CTRL + J", hl.dsp.focus({ direction = "down" }), _F)
|
||||||
|
|
||||||
|
-- ── Resize (add hjkl to HyDE's arrow set) ────────────────────────────────────
|
||||||
|
-- HyDE puts a flagless calculator menu on Super+Shift+K; our repeating resize
|
||||||
|
-- would double-fire next to it (flag mismatch), so drop the calculator.
|
||||||
|
unbind_all(MOD .. " + SHIFT + K") -- displaces HyDE: calculator menu
|
||||||
|
for _, m in ipairs({ { "L", 30, 0 }, { "H", -30, 0 }, { "K", 0, -30 }, { "J", 0, 30 } }) do
|
||||||
|
_F = { description = "[Window Management|Resize Active Window] resize window", repeating = true }
|
||||||
|
hl.bind(MOD .. " + SHIFT + " .. m[1], hl.dsp.window.resize({ x = m[2], y = m[3], relative = true }), _F)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ── Move active window (Super+Alt, float-aware: moveactive / swapwindow) ─────
|
||||||
|
-- Displaces HyDE: wallpaper next/prev (Alt+Right/Left) and waybar layout
|
||||||
|
-- next/prev (Alt+Up/Down); wallpaper moves to Super+Alt+Ctrl+Shift below,
|
||||||
|
-- as in the dots chain. HyDE's binds carry no flags but ours are repeating,
|
||||||
|
-- so an explicit unbind is needed to keep them from double-firing.
|
||||||
|
unbind_all(MOD .. " + ALT + RIGHT")
|
||||||
|
unbind_all(MOD .. " + ALT + LEFT")
|
||||||
|
unbind_all(MOD .. " + ALT + UP")
|
||||||
|
unbind_all(MOD .. " + ALT + DOWN")
|
||||||
|
local function dots_move_window(dir, pix)
|
||||||
|
local lut = { l = { -1, 0 }, r = { 1, 0 }, u = { 0, -1 }, d = { 0, 1 } }
|
||||||
|
local m = lut[dir]
|
||||||
|
return function()
|
||||||
|
local w = hl.get_active_window()
|
||||||
|
if w and w.floating then
|
||||||
|
hl.dispatch(hl.dsp.window.move({ x = m[1] * pix, y = m[2] * pix, relative = true }))
|
||||||
|
else
|
||||||
|
local names = { l = "left", r = "right", u = "up", d = "down" }
|
||||||
|
hl.dispatch(hl.dsp.window.swap({ direction = names[dir] }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, m in ipairs({
|
||||||
|
{ "LEFT", "l" },
|
||||||
|
{ "RIGHT", "r" },
|
||||||
|
{ "UP", "u" },
|
||||||
|
{ "DOWN", "d" },
|
||||||
|
{ "H", "l" },
|
||||||
|
{ "L", "r" },
|
||||||
|
{ "K", "u" },
|
||||||
|
{ "J", "d" },
|
||||||
|
}) do
|
||||||
|
_F = { description = "[Window Management|Move active window] move " .. m[2], repeating = true }
|
||||||
|
hl.bind(MOD .. " + ALT + " .. m[1], dots_move_window(m[2], 30), _F)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ── Wallpaper (moved off Super+Alt by the dots chain) ────────────────────────
|
||||||
|
_F = { description = "[Theming and Wallpaper] next global wallpaper" }
|
||||||
|
hl.bind(MOD .. " + ALT + CTRL + SHIFT + RIGHT", hl.dsp.exec_cmd(hyde.sh.wallpaper("--next")), _F)
|
||||||
|
_F = { description = "[Theming and Wallpaper] previous global wallpaper" }
|
||||||
|
hl.bind(MOD .. " + ALT + CTRL + SHIFT + LEFT", hl.dsp.exec_cmd(hyde.sh.wallpaper("--prev")), _F)
|
||||||
|
|
||||||
|
-- ── Workspaces ───────────────────────────────────────────────────────────────
|
||||||
|
-- r~N = Nth workspace of the FOCUSED monitor — load-bearing on the 3-monitor
|
||||||
|
-- workspace sets (1-10 / 11-20 / 21-30 pinned per monitor in host.lua).
|
||||||
|
-- HyDE's own binds pass "r+1"/"empty" strings the same way; r~N is the one
|
||||||
|
-- thing to verify first in the live session.
|
||||||
|
for i = 1, 10 do
|
||||||
|
local key = (i == 10) and 0 or i
|
||||||
|
_F = { description = "[Workspaces|Navigation] navigate to workspace " .. i .. " (monitor-relative)" }
|
||||||
|
hl.bind(MOD .. " + " .. key, hl.dsp.focus({ workspace = "r~" .. i }), _F) -- displaces HyDE: absolute workspace N
|
||||||
|
_F = { description = "[Workspaces|Move window to workspace] move to workspace " .. i .. " (monitor-relative)" }
|
||||||
|
hl.bind(MOD .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = "r~" .. i }), _F)
|
||||||
|
_F = { description = "[Workspaces|Move window silently] move to workspace " .. i .. " (monitor-relative)" }
|
||||||
|
hl.bind(MOD .. " + ALT + " .. key, hl.dsp.window.move({ workspace = "r~" .. i, follow = false }), _F) -- displaces HyDE: absolute silent move
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Relative workspace nav on Super+Ctrl+Shift (dots), displacing HyDE's
|
||||||
|
-- move-window-in-workspace family there (repeating flags -> explicit unbind;
|
||||||
|
-- dots does window movement on Super+Alt instead).
|
||||||
|
unbind_all(MOD .. " + SHIFT + CTRL + LEFT")
|
||||||
|
unbind_all(MOD .. " + SHIFT + CTRL + RIGHT")
|
||||||
|
unbind_all(MOD .. " + SHIFT + CTRL + UP")
|
||||||
|
unbind_all(MOD .. " + SHIFT + CTRL + DOWN")
|
||||||
|
_F = { description = "[Workspaces|Navigation|Relative workspace] change active workspace forwards" }
|
||||||
|
hl.bind(MOD .. " + CTRL + SHIFT + RIGHT", hl.dsp.focus({ workspace = "r+1" }), _F)
|
||||||
|
hl.bind(MOD .. " + CTRL + SHIFT + L", hl.dsp.focus({ workspace = "r+1" }), _F)
|
||||||
|
_F = { description = "[Workspaces|Navigation|Relative workspace] change active workspace backwards" }
|
||||||
|
hl.bind(MOD .. " + CTRL + SHIFT + LEFT", hl.dsp.focus({ workspace = "r-1" }), _F)
|
||||||
|
hl.bind(MOD .. " + CTRL + SHIFT + H", hl.dsp.focus({ workspace = "r-1" }), _F)
|
||||||
|
|
||||||
|
_F = { description = "[Workspaces|Navigation|Relative workspace] change active workspace forwards" }
|
||||||
|
hl.bind(MOD .. " + RIGHT", hl.dsp.focus({ workspace = "r+1" }), _F)
|
||||||
|
_F = { description = "[Workspaces|Navigation|Relative workspace] change active workspace backwards" }
|
||||||
|
hl.bind(MOD .. " + LEFT", hl.dsp.focus({ workspace = "r-1" }), _F)
|
||||||
|
_F = { description = "[Workspaces|Move window to workspace|Relative workspace] move focused window to next workspace" }
|
||||||
|
hl.bind(MOD .. " + CTRL + RIGHT", hl.dsp.window.move({ workspace = "r+1" }), _F) -- displaces HyDE: workspace r+1
|
||||||
|
_F = {
|
||||||
|
description = "[Workspaces|Move window to workspace|Relative workspace] move focused window to previous workspace",
|
||||||
|
}
|
||||||
|
hl.bind(MOD .. " + CTRL + LEFT", hl.dsp.window.move({ workspace = "r-1" }), _F) -- displaces HyDE: workspace r-1
|
||||||
|
|
||||||
|
-- E for "empty" — Super+Ctrl+Down is taken by movefocus above
|
||||||
|
_F = { description = "[Workspaces|Navigation] navigate to the nearest empty workspace" }
|
||||||
|
hl.bind(MOD .. " + CTRL + E", hl.dsp.focus({ workspace = "empty" }), _F)
|
||||||
|
|
||||||
|
-- Move window to relative workspace: HyDE ships the arrow pair; add hjkl.
|
||||||
|
_F = { description = "[Workspaces|Move window to workspace|Relative workspace] move focused window to next workspace" }
|
||||||
|
hl.bind(MOD .. " + CTRL + ALT + L", hl.dsp.window.move({ workspace = "r+1" }), _F)
|
||||||
|
_F = {
|
||||||
|
description = "[Workspaces|Move window to workspace|Relative workspace] move focused window to previous workspace",
|
||||||
|
}
|
||||||
|
hl.bind(MOD .. " + CTRL + ALT + H", hl.dsp.window.move({ workspace = "r-1" }), _F)
|
||||||
|
|
||||||
|
-- ── Hardware controls ────────────────────────────────────────────────────────
|
||||||
|
-- HyDE binds bare F10/F11/F12 to mute / volume down / volume up (key_binds.lua,
|
||||||
|
-- locked + repeating -- a plain rebind would leave both live). That is a laptop
|
||||||
|
-- F-row assumption: the Cantor has no F-row, so F1-F12 only ever arrive from its
|
||||||
|
-- Vial layer 5 and have to reach applications instead of the mixer. The
|
||||||
|
-- XF86Audio* binds below them keep the media keys working, so nothing is lost,
|
||||||
|
-- and Shift+F11 (cycle fullscreen) canonicalizes separately and survives.
|
||||||
|
unbind_all("F10") -- displaces HyDE: un/mute output
|
||||||
|
unbind_all("F11") -- displaces HyDE: decrease volume
|
||||||
|
unbind_all("F12") -- displaces HyDE: increase volume
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
-- gui package — the dots layer + window rules, split out of ./hyprland.lua
|
||||||
|
-- (required there in their original position). Plain hl.* registrations;
|
||||||
|
-- nothing here depends on the rest of the layer.
|
||||||
|
|
||||||
|
-- ── Layer rules ──────────────────────────────────────────────────────────────
|
||||||
|
-- waybar blur ships upstream (layer_rules.lua), but its ignore_alpha uses the
|
||||||
|
-- boolean form (default threshold), which drops the blur on low-alpha regions
|
||||||
|
-- — the cavabar background is rgba(0,0,0,0.2) and went flat. The legacy chain
|
||||||
|
-- set the threshold to 0 (blur everything) on both bars; restore that exactly.
|
||||||
|
hl.layer_rule({
|
||||||
|
name = "dots_cavabar_blur",
|
||||||
|
match = { namespace = "cavabar" },
|
||||||
|
blur = true,
|
||||||
|
ignore_alpha = 0,
|
||||||
|
})
|
||||||
|
hl.layer_rule({
|
||||||
|
name = "dots_waybar_ignore_alpha",
|
||||||
|
match = { namespace = "waybar" },
|
||||||
|
ignore_alpha = 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- ── Window rules ─────────────────────────────────────────────────────────────
|
||||||
|
-- Upstream's window_rules.lua consolidates most floats (dialogs, portals,
|
||||||
|
-- dolphin, nm-*, qt*ct, vlc, pavucontrol, …) and the PiP/modal handling.
|
||||||
|
-- Ported here: everything the repo's windowrules.conf had that upstream
|
||||||
|
-- dropped when it went global-opacity — per-app opacity, idle_inhibit,
|
||||||
|
-- jetbrains popups — plus the floats upstream does not cover.
|
||||||
|
|
||||||
|
-- idle_inhibit while fullscreen (media players, browsers)
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_idle_inhibit_media",
|
||||||
|
match = { class = "^(.*celluloid.*)$|^(.*mpv.*)$|^(.*vlc.*)$|^(.*[Ss]potify.*)$" },
|
||||||
|
idle_inhibit = "fullscreen",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_idle_inhibit_browsers",
|
||||||
|
match = {
|
||||||
|
class = "^(.*LibreWolf.*)$|^(.*floorp.*)$|^(.*brave-browser.*)$|^(.*firefox.*)$|^(.*chromium.*)$|^(.*zen.*)$|^(.*vivaldi.*)$",
|
||||||
|
},
|
||||||
|
idle_inhibit = "fullscreen",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- per-app opacity ("active inactive fullscreen", hyprlang-style value string --
|
||||||
|
-- the Lua rule's opacity field is string-typed and takes the whole triple)
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_browsers",
|
||||||
|
match = { class = "^(firefox)$|^(zen)$|^(brave-browser)$" },
|
||||||
|
opacity = "0.90 0.90 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_editors_terms",
|
||||||
|
match = {
|
||||||
|
class = "^(code-oss)$|^([Cc]ode)$|^(code-url-handler)$|^(code-insiders-url-handler)$|^(kitty)$|^(org.kde.dolphin)$|^(org.kde.ark)$|^(nwg-look)$|^(qt5ct)$|^(qt6ct)$|^(kvantummanager)$",
|
||||||
|
},
|
||||||
|
opacity = "0.80 0.80 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_applets",
|
||||||
|
match = {
|
||||||
|
class = "^(org.pulseaudio.pavucontrol)$|^(blueman-manager)$|^(nm-applet)$|^(nm-connection-editor)$|^(hyprpolkitagent)$|^(org.freedesktop.impl.portal.desktop.gtk)$|^(org.freedesktop.impl.portal.desktop.hyprland)$",
|
||||||
|
},
|
||||||
|
opacity = "0.80 0.70 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_spotify_class",
|
||||||
|
match = { class = "^([Ss]potify)$" },
|
||||||
|
opacity = "0.70 0.70 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_spotify_title",
|
||||||
|
match = { initial_title = "^(Spotify Free)$|^(Spotify Premium)$" },
|
||||||
|
opacity = "0.70 0.70 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_blender",
|
||||||
|
match = { class = "^(blender)$" },
|
||||||
|
opacity = "1.00 1.00 1",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_gtk_apps",
|
||||||
|
match = { class = "^(com.github.rafostar.Clapper)$" },
|
||||||
|
opacity = "0.90 0.90",
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_opacity_misc_apps",
|
||||||
|
match = {
|
||||||
|
class = "^(com.github.tchx84.Flatseal)$|^(hu.kramo.Cartridges)$|^(com.obsproject.Studio)$|^(gnome-boxes)$|^(vesktop)$|^(discord)$|^(WebCord)$|^(ArmCord)$|^(app.drey.Warp)$|^(net.davidotek.pupgui2)$|^(yad)$|^(Signal)$|^(io.github.alainm23.planify)$|^(io.gitlab.theevilskeleton.Upscaler)$|^(com.github.unrud.VideoDownloader)$|^(io.gitlab.adhami3310.Impression)$|^(io.missioncenter.MissionCenter)$|^(io.github.flattool.Warehouse)$",
|
||||||
|
},
|
||||||
|
opacity = "0.80 0.80",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- floats upstream's consolidated list does not cover
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_float_class",
|
||||||
|
match = {
|
||||||
|
class = "^(Signal)$|^(com.github.rafostar.Clapper)$|^(app.drey.Warp)$|^(net.davidotek.pupgui2)$|^(yad)$|^(eog)$|^(io.github.alainm23.planify)$|^(io.gitlab.theevilskeleton.Upscaler)$|^(com.github.unrud.VideoDownloader)$|^(io.gitlab.adhami3310.Impression)$|^(io.missioncenter.MissionCenter)$",
|
||||||
|
},
|
||||||
|
float = true,
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_float_title",
|
||||||
|
match = { title = "^(About Mozilla Firefox)$|^(Ghidra: NO ACTIVE PROJECT.*)$" },
|
||||||
|
float = true,
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_float_kitty_monitors",
|
||||||
|
match = { class = "^(kitty)$", title = "^(top)$|^(btop)$|^(htop)$" },
|
||||||
|
float = true,
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_blender_image_editor",
|
||||||
|
match = { class = "^(blender)$", initial_title = "^(Image Editor)$" },
|
||||||
|
float = true,
|
||||||
|
size = "(monitor_w*0.5) (monitor_h*0.5)",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- jetbrains dropdowns/popups flicker workaround
|
||||||
|
hl.window_rule({
|
||||||
|
name = "dots_jetbrains_popups",
|
||||||
|
match = { class = "^(.*jetbrains.*)$", title = "^(win[0-9]+)$" },
|
||||||
|
no_initial_focus = true,
|
||||||
|
})
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Default (empty) per-host extras — fallback shared by all GUI hosts.
|
|
||||||
# Real host.conf comes from the per-host overlay (e.g. wm/ sources nvidia.conf here).
|
|
||||||
# Present so `source = ./host.conf` in hyprland.conf always resolves, even mid-install
|
|
||||||
# before the host overlay is stowed (avoids the transient "source globbing error").
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
#! ░▒▒▒░░░▓▓ ___________
|
|
||||||
#! ░░▒▒▒░░░░░▓▓ //___________/
|
|
||||||
#! ░░▒▒▒░░░░░▓▓ _ _ _ _ _____
|
|
||||||
#! ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/
|
|
||||||
#! ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___
|
|
||||||
#! ░▒▒░░▓▓ ▓▓ \__ |____/ |____/
|
|
||||||
#! ░▒▓▓ ▓▓ //____/
|
|
||||||
|
|
||||||
# Use this for reference to override the default HyDE' hyprland configuration
|
|
||||||
|
|
||||||
# ! Never source ~/.config/hypr/hyde.conf directly, it is sourced by the main configuration file
|
|
||||||
# This file acts as an override configuration for the user to set their environment variables and startup commands
|
|
||||||
# Static variable declaration in hyde.conf will be prioritized over the default and dynamic configuration
|
|
||||||
|
|
||||||
# NOTE
|
|
||||||
# Leaving the variable empty will unset the variable
|
|
||||||
# Commenting out the variable will use the default value
|
|
||||||
# For updated configuration options, see https://github.com/HyDE-Project/HyDE/blob/master/Configs/.config/hypr/hyde.conf
|
|
||||||
# For simplicity, ./hyde.conf ONLY accepts $ for variables and # for comments, will sanitize the file to remove any other characters
|
|
||||||
# $start.VAR , $env.VAR are ONLY HyDE specific conventions for consistency.
|
|
||||||
|
|
||||||
# Keyboard modifier
|
|
||||||
# $mainMod = SUPER # windows key
|
|
||||||
|
|
||||||
|
|
||||||
# ▄▀█ █▀█ █▀█ █▀
|
|
||||||
# █▀█ █▀▀ █▀▀ ▄█
|
|
||||||
|
|
||||||
# $QUICKAPPS = # used for quick app launcher
|
|
||||||
# Leaving this to HyDE means Super+B runs its `$default.BROWSER`, which is
|
|
||||||
# `hyde-launch.sh --fall firefox web-browser` — and the 26.x update deprecated that script, so
|
|
||||||
# every press fired a "hyde-launch.sh is deprecated, please use hyde-shell open instead"
|
|
||||||
# notification. Naming the browser directly skips the wrapper entirely. This is the primary
|
|
||||||
# browser only; Super+Shift+B (librewolf) and Super+Ctrl+B (firefox) are set in keybindings.conf.
|
|
||||||
# `hyde-shell open web-browser` is the modern HyDE equivalent if the xdg default should decide.
|
|
||||||
$BROWSER = zen-browser # matches the xdg default (zen.desktop)
|
|
||||||
# $EDITOR = code # default editor, if commented out , will use the default editor
|
|
||||||
# Same deprecation as $BROWSER above: HyDE's default is `hyde-launch.sh --fall dolphin
|
|
||||||
# file-manager`, and Super+E in keybindings.conf uses it. $EDITOR is deliberately left to HyDE —
|
|
||||||
# no active bind reads it (Super+C runs `$TERMINAL nvim`), so there is nothing to fix there.
|
|
||||||
$EXPLORER = dolphin # matches the xdg default (org.kde.dolphin.desktop)
|
|
||||||
# $TERMINAL = kitty # default terminal, if commented out , will use the org.gnome.desktop.default-applications.terminal
|
|
||||||
# $LOCKSCREEN=hyprlock # default lockscreen, you can use any lockscreen you want, eg swaylock
|
|
||||||
# $IDLE=hypridle # default idle manager, you can use any idle manager you want,eg swayidle
|
|
||||||
|
|
||||||
|
|
||||||
# // █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█
|
|
||||||
# // █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Keywords/
|
|
||||||
# Override the default startup commands
|
|
||||||
|
|
||||||
# $start.XDG_PORTAL_RESET=$scrPath/resetxdgportal.sh
|
|
||||||
# $start.DBUS_SHARE_PICKER=dbus-update-activation-environment --systemd --all # for XDPH
|
|
||||||
# $start.SYSTEMD_SHARE_PICKER=systemctl --user import-environment QT_QPA_PLATFORMTHEME WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH
|
|
||||||
# Empty = unset, so HyDE's default `exec-once = $start.BAR` (a bare `waybar`, using HyDE's
|
|
||||||
# generated config.jsonc/style.css) does not launch a second bar. The bar we actually want is
|
|
||||||
# launched from userprefs.conf with the custom layout/style.
|
|
||||||
$start.BAR=
|
|
||||||
# $start.NOTIFICATIONS=swaync # dunst
|
|
||||||
# $start.APPTRAY_BLUETOOTH=blueman-applet
|
|
||||||
# $start.WALLPAPER=$scrPath/wallpaper.sh --global # 26.x: backend-agnostic (awww); swwwallpaper.sh is gone
|
|
||||||
# $start.TEXT_CLIPBOARD=wl-paste --type text --watch cliphist store
|
|
||||||
# $start.IMAGE_CLIPBOARD=wl-paste --type image --watch cliphist store
|
|
||||||
# $start.BATTERY_NOTIFY=$scrPath/batterynotify.sh
|
|
||||||
# Kept explicitly at the default: nm-applet's tray icon is the one place with a quick
|
|
||||||
# connect/disconnect MENU (wifi list, VPN) — the bar's `network` module only shows state
|
|
||||||
# and bandwidth. Tela renders it as a blue circle (its scalable/devices icon); that's the
|
|
||||||
# theme working, not a bug. Was briefly unset 2026-08-14 — wrong call, the menu was wanted.
|
|
||||||
$start.NETWORK_MANAGER=nm-applet --indicator
|
|
||||||
# $start.REMOVABLE_MEDIA=udiskie --no-automount --smart-tray
|
|
||||||
# $start.AUTH_DIALOGUE=$scrPath/polkitkdeauth.sh
|
|
||||||
# $start.IDLE_DAEMON=$IDLE
|
|
||||||
|
|
||||||
# // █▀▀ █▄░█ █░█
|
|
||||||
# // ██▄ █░▀█ ▀▄▀
|
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Environment-variables/
|
|
||||||
# Override the default environment variables
|
|
||||||
|
|
||||||
|
|
||||||
# # Toolkit Backend Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#toolkit-backend-variables
|
|
||||||
# $env.GDK_BACKEND = wayland,x11,* #s GTK: Use wayland if available. If not: try x11, then any other GDK backend.
|
|
||||||
# # $env.QT_QPA_PLATFORM = wayland;xcb #Qt: Use wayland if available, fall back to x11 if not.
|
|
||||||
# $env.SDL_VIDEODRIVER = wayland #s Run SDL2 applications on Wayland. Remove or set to x11 if games that provide older versions of SDL cause compatibility issues
|
|
||||||
# $env.CLUTTER_BACKEND = wayland #s Clutter package already has wayland enabled, this variable will force Clutter applications to try and use the Wayland backend
|
|
||||||
|
|
||||||
# # XDG Specifications - https://wiki.hyprland.org/Configuring/Environment-variables/#xdg-specifications
|
|
||||||
# $env.XDG_CURRENT_DESKTOP = Hyprland
|
|
||||||
# $env.XDG_SESSION_TYPE = wayland
|
|
||||||
# $env.XDG_SESSION_DESKTOP = Hyprland
|
|
||||||
|
|
||||||
# # Qt Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#qt-variables
|
|
||||||
|
|
||||||
# $env.QT_AUTO_SCREEN_SCALE_FACTOR = 1 # (From the Qt documentation) enables automatic scaling, based on the monitor’s pixel density
|
|
||||||
# $env.QT_QPA_PLATFORM=wayland;xcb # Tell Qt applications to use the Wayland backend, and fall back to x11 if Wayland is unavailable
|
|
||||||
# $env.QT_WAYLAND_DISABLE_WINDOWDECORATION = 1 # Disables window decorations on Qt applications
|
|
||||||
# $env.QT_QPA_PLATFORMTHEME = qt6ct # Tells Qt based applications to pick your theme from qt5ct, use with Kvantum.
|
|
||||||
|
|
||||||
# # HyDE Environment Variables -
|
|
||||||
|
|
||||||
# $env.PATH =
|
|
||||||
# $env.MOZ_ENABLE_WAYLAND=1 # Enable Wayland for Firefox
|
|
||||||
# $env.GDK_SCALE=1 # Set GDK scale to 1 // For Xwayland on HiDPI
|
|
||||||
# $env.ELECTRON_OZONE_PLATFORM_HINT=auto # Set Electron Ozone Platform Hint to auto // For Electron apps on Wayland
|
|
||||||
|
|
||||||
# # XDG-DIRS
|
|
||||||
|
|
||||||
# $env.XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR
|
|
||||||
# $env.XDG_CONFIG_HOME=$HOME/.config
|
|
||||||
# $env.XDG_CACHE_HOME=$HOME/.cache
|
|
||||||
# $env.XDG_DATA_HOME=$HOME/.local/share
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# These variable will override the default and the theme configuration
|
|
||||||
# Do not uncomment the variables if you want want HyDE do the theme configuration for you
|
|
||||||
|
|
||||||
# // █▀▀ ▀█▀ █▄▀
|
|
||||||
# // █▄█ ░█░ █░█
|
|
||||||
|
|
||||||
|
|
||||||
#$GTK_THEME=Wallbash-Gtk
|
|
||||||
# dots-icons = repo-tracked overlay theme (gui/.local/share/icons/dots-icons) that inherits
|
|
||||||
# Tela-circle-dracula and only swaps the tray/status icons for Tela's monochrome panel set at
|
|
||||||
# every size — without it, waybar on a fractionally-scaled monitor requests 2x sizes and gets
|
|
||||||
# the colored-circle variants (the blue nm-applet blob). Everything else still comes from Tela.
|
|
||||||
$ICON_THEME=dots-icons
|
|
||||||
#$COLOR_SCHEME=prefer-dark
|
|
||||||
|
|
||||||
# // █▀▀ █░█ █▀█ █▀ █▀█ █▀█
|
|
||||||
# // █▄▄ █▄█ █▀▄ ▄█ █▄█ █▀▄
|
|
||||||
|
|
||||||
#$CURSOR_THEME=Bibata-Modern-Ice
|
|
||||||
#$CURSOR_SIZE=30
|
|
||||||
|
|
||||||
# // █▀▀ █▀█ █▄░█ ▀█▀
|
|
||||||
# // █▀░ █▄█ █░▀█ ░█░
|
|
||||||
|
|
||||||
#$FONT=Canterell
|
|
||||||
#$FONT_SIZE=10
|
|
||||||
#$DOCUMENT_FONT=Cantarell
|
|
||||||
#$DOCUMENT_FONT_SIZE=10
|
|
||||||
#$MONOSPACE_FONT=CaskaydiaCove Nerd Font Mono
|
|
||||||
#$MONOSPACE_FONT_SIZE=9
|
|
||||||
#$FONT_ANTIALIASING=rgba
|
|
||||||
#$FONT_HINTING=full
|
|
||||||
|
|
||||||
|
|
||||||
# // █░░ █▀█ █▀▀ █▄▀ █▀ █▀▀ █▀█ █▀▀ █▀▀ █▄░█
|
|
||||||
# // █▄▄ █▄█ █▄▄ █░█ ▄█ █▄▄ █▀▄ ██▄ ██▄ █░▀█
|
|
||||||
|
|
||||||
#Setting the Hyprlock layout will override any layout set in the ./hypr/hyprlock.sh
|
|
||||||
# Dynamic Hyprlock layout should be set in the ./hypr/hyprlock.sh file
|
|
||||||
# $LAYOUT_PATH=/path/to/hyprlock/layout.conf
|
|
||||||
@@ -7,7 +7,14 @@
|
|||||||
#! ░▒▓▓ ▓▓ //____/
|
#! ░▒▓▓ ▓▓ //____/
|
||||||
|
|
||||||
# $LOCKSCREEN = hyprlock # preferred LOCKSCREEN e.g swaylock
|
# $LOCKSCREEN = hyprlock # preferred LOCKSCREEN e.g swaylock
|
||||||
$LOCKSCREEN = lockscreen.sh # Calls $LOCKSCREEN set from hyprland
|
# Absolute path required: the Lua chain starts hypridle as a systemd user unit
|
||||||
|
# (hyde-Hyprland-idle.service), and on the SDDM/fish session path the session PATH
|
||||||
|
# never reaches the user manager (start_up.lua's unconditional `uwsm finalize` runs
|
||||||
|
# but errors outside a uwsm session, so the import never happens) — a bare
|
||||||
|
# `lockscreen.sh` is "command not found" there — Super+L, the 240s idle lock and
|
||||||
|
# before_sleep all silently no-op. The script self-bootstraps its own env
|
||||||
|
# (hyde-shell init), so the full path alone is sufficient.
|
||||||
|
$LOCKSCREEN = ~/.local/lib/hyde/lockscreen.sh
|
||||||
|
|
||||||
general {
|
general {
|
||||||
lock_cmd = $LOCKSCREEN
|
lock_cmd = $LOCKSCREEN
|
||||||
@@ -31,10 +38,14 @@ listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# DPMS off
|
# DPMS off
|
||||||
|
# Lua-chain form: in Lua mode `hyprctl dispatch` evaluates its argument as Lua, so the hyprlang
|
||||||
|
# `dpms off` is a syntax error and this listener silently never ran. The table form is the one
|
||||||
|
# hypridle's own sample uses (/usr/share/hypr/hypridle.conf); a bare bool/string argument acts
|
||||||
|
# like a toggle — `hl.dsp.dpms("on")` turned the display OFF. main (hyprlang) keeps `dpms off/on`.
|
||||||
listener {
|
listener {
|
||||||
timeout = 2300
|
timeout = 2300
|
||||||
on-timeout = hyprctl dispatch dpms off #do not turn off display while media is playing
|
on-timeout = hyprctl dispatch 'hl.dsp.dpms({action = "off"})' #do not turn off display while media is playing
|
||||||
on-resume = hyprctl dispatch dpms on
|
on-resume = hyprctl dispatch 'hl.dsp.dpms({action = "on"})'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Suspend
|
# Suspend
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
$HYDE_HYPRLAND=set #! Do not remove - HyDE marker to prevent file overwrite
|
|
||||||
|
|
||||||
#! ░▒▒▒░░░▓▓ ___________
|
|
||||||
#! ░░▒▒▒░░░░░▓▓ //___________/
|
|
||||||
#! ░░▒▒▒░░░░░▓▓ _ _ _ _ _____
|
|
||||||
#! ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/
|
|
||||||
#! ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___
|
|
||||||
#! ░▒▒░░▓▓ ▓▓ \__ |____/ |____/
|
|
||||||
#! ░▒▓▓ ▓▓ //____/
|
|
||||||
|
|
||||||
#? HyDE's Hyprland Configuration
|
|
||||||
# First, let's understand the structure of the configuration file
|
|
||||||
# It is divided into 3 sections: Boilerplate, Overrides, and Users
|
|
||||||
#* 1. Boilerplate
|
|
||||||
# - This section is for the default configuration of HyDE
|
|
||||||
# - It is recommended to not change this section
|
|
||||||
# - Filepath: $XDG_DATA_HOME/share/hyde/hyprland.conf
|
|
||||||
#* 2. Overrides
|
|
||||||
# - This section is for overriding the default configuration of HyDE
|
|
||||||
# - Change this section if you intended to:
|
|
||||||
# - Change the startup and env variables
|
|
||||||
# - Stop a app/service from starting
|
|
||||||
# - Override HyDE specific variables
|
|
||||||
# - Filepath: $XDG_CONFIG_HOME/hypr/hyde.conf
|
|
||||||
#* 3. Users
|
|
||||||
# - This section is for user configuration
|
|
||||||
# - It is recommended to change this section to your liking
|
|
||||||
# - Filepath: ./keybindings.conf, ./windowrules.conf, ./monitors.conf, ./userprefs.conf
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#? Boilerplate
|
|
||||||
source = ~/.local/share/hyde/hyprland.conf
|
|
||||||
|
|
||||||
|
|
||||||
#? Overrides
|
|
||||||
# Overriding startup and env variables
|
|
||||||
# To avoid breaking and conflicts, HyDE will use variables to store the env and startup variables
|
|
||||||
# See ~/.config/hypr/hyde.conf for overriding the default env and startup variables
|
|
||||||
#* startup:
|
|
||||||
# - The startup variables are the variables that are set when the shell starts
|
|
||||||
# - $start.BAR=waybar will launch waybar on startup (default waybar)
|
|
||||||
# - $start.BAR= will unset the variable
|
|
||||||
# - $start.BAR=other_bar will launch other_bar on startup, overriding the default (waybar)
|
|
||||||
#* env:
|
|
||||||
# - The env variables are the variables that are set when the shell starts
|
|
||||||
# - $env.QT_QPA_PLATFORMTHEME= will unset the variable
|
|
||||||
# - $env.QT_QPA_PLATFORMTHEME=qt6ct will set the variable to qt6ct
|
|
||||||
# for updated configuration, see the https://github.com/HyDE-Project/HyDE/blob/master/Configs/.config/hypr/hyde.conf
|
|
||||||
|
|
||||||
|
|
||||||
#? Users
|
|
||||||
# These files are for user configuration
|
|
||||||
# Edit the files to your liking
|
|
||||||
# Refer to https://wiki.hyprland.org
|
|
||||||
source = ./keybindings.conf # Keyboard shortcuts
|
|
||||||
source = ./windowrules.conf # Window rules
|
|
||||||
source = ./monitors.conf # Monitor configuration (from per-host overlay)
|
|
||||||
source = ./userprefs.conf # Personal hyprland configuration
|
|
||||||
source = ./host.conf # Per-host extras: nvidia, etc. (from per-host overlay)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#!▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄
|
|
||||||
|
|
||||||
#? █▀█ █▀▀ █▀▀ █▀▀ █▀█ █▀▀ █▄░█ █▀▀ █▀▀
|
|
||||||
#? █▀▄ ██▄ █▀░ ██▄ █▀▄ ██▄ █░▀█ █▄▄ ██▄
|
|
||||||
|
|
||||||
#!▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄ ▄▄
|
|
||||||
|
|
||||||
|
|
||||||
#? Reference
|
|
||||||
# Configurations below are for reference ONLY
|
|
||||||
# They are not essential for HyDE to work but can be used to enhance QOL
|
|
||||||
# Copy the config line and paste it in the Users section to use it
|
|
||||||
# This is to prevent bloating users configuration files when not needed
|
|
||||||
|
|
||||||
# // █▀▀ █▄░█ █░█
|
|
||||||
# // ██▄ █░▀█ ▀▄▀
|
|
||||||
|
|
||||||
# env = MANGOHUD,1
|
|
||||||
# env = MANGOHUD_DLSYM,1
|
|
||||||
|
|
||||||
|
|
||||||
# // █▄▀ █▀▀ █▄█ █▄▄ █ █▄░█ █▀▄ █ █▄░█ █▀▀ █▀
|
|
||||||
# // █░█ ██▄ ░█░ █▄█ █ █░▀█ █▄▀ █ █░▀█ █▄█ ▄█
|
|
||||||
|
|
||||||
# bind = Ctrl+Alt+Shift, R, pass, ^(com\.obsproject\.Studio)$ # toggle obs screen recording // install obs flatpak
|
|
||||||
# bind = $mainMod+Shift, G, exec, pkill -x rofi || $scrPath/gamelauncher.sh # launch steam game launcher // install steam
|
|
||||||
# bind = $mainMod, Period, exec, emote # launch emoji selector // install emote
|
|
||||||
# bind = $mainMod+Alt, XF86MonBrightnessDown, exec, hyprshade on blue-light-filter # enable blue light filter // install hyprshade
|
|
||||||
# bind = $mainMod+Alt, XF86MonBrightnessUp, exec, hyprshade off # disable blue light filter // install hyprshade
|
|
||||||
|
|
||||||
|
|
||||||
# // █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█
|
|
||||||
# // █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█
|
|
||||||
|
|
||||||
# exec-once = swayidle -w timeout 600 'swaylock' timeout 900 'hyprctl dispatch dpms off' resume 'hyprctl dispatch dpms on' # lock after 10 mins, sleep after 15 mins // install swayidle
|
|
||||||
# exec-once = swayidle -w timeout 1200 'swaylock; hyprctl dispatch dpms off' resume 'hyprctl dispatch dpms on' timeout 1800 'systemctl suspend' # lock and sleep after 20 mins, suspend after 30 mins // install swayidle
|
|
||||||
|
|
||||||
# exec-once = libinput-gestures // install libinput-gestures
|
|
||||||
|
|
||||||
# exec-once = swayosd-server # enable swayosd service
|
|
||||||
# exec-once = `pkexec swayosd-libinput-backend` # swayosd service for keyboard input (requires to be run in a subshell)
|
|
||||||
|
|
||||||
# // █░█░█ █ █▄░█ █▀▄ █▀█ █░█░█ █▀█ █░█ █░░ █▀▀ █▀
|
|
||||||
# // ▀▄▀▄▀ █ █░▀█ █▄▀ █▄█ ▀▄▀▄▀ █▀▄ █▄█ █▄▄ ██▄ ▄█
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# // █░█ █▀▄▀█
|
|
||||||
# // ▀▄▀ █░▀░█
|
|
||||||
|
|
||||||
# bind = Ctrl+Alt_L, V, submap, passthrough
|
|
||||||
# submap = passthrough
|
|
||||||
# bind = Ctrl+Alt_L, V, submap, reset
|
|
||||||
# submap = reset
|
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
-- gui package — the dots Hyprland layer, ported from the hyprlang chain
|
||||||
|
-- (hyde.conf / userprefs.conf / keybindings.conf / windowrules.conf) to
|
||||||
|
-- HyDE 26.x's Lua config. Loaded by ~/.local/share/hypr/hyde.lua second-to-last
|
||||||
|
-- (after every HyDE module, before lua_state.workflows), so plain assignments
|
||||||
|
-- here override HyDE's defaults. HyDE never overwrites this file.
|
||||||
|
--
|
||||||
|
-- The rules and keybinds sections live in ./dots_rules.lua and
|
||||||
|
-- ./dots_binds.lua, required in place below. Host-specific config (monitors,
|
||||||
|
-- workspace pins, gaps/rounding, gpu extras) lives in ./host.lua, loaded at
|
||||||
|
-- the bottom — same layering the hyprlang chain had with
|
||||||
|
-- `source = ./host.conf`.
|
||||||
|
|
||||||
|
-- Bootstrap: Hyprland prefers hyprland.lua as the entry point when started
|
||||||
|
-- without a config; without this block that path yields a bare cursor. hyde.lua
|
||||||
|
-- sets `hyde` on its first line, so this runs only when HyDE has not loaded.
|
||||||
|
-- The `return` matters: dofile(hyde.lua) re-enters this file through
|
||||||
|
-- check_require("hyprland"), so by the time dofile returns the body below has
|
||||||
|
-- already run once — falling through would register everything twice.
|
||||||
|
if not hyde then
|
||||||
|
local share = os.getenv("XDG_DATA_HOME") or (os.getenv("HOME") .. "/.local/share")
|
||||||
|
local entry = share .. "/hypr/hyde.lua"
|
||||||
|
local handle = io.open(entry, "r")
|
||||||
|
if not handle then
|
||||||
|
error(
|
||||||
|
"HyDE is not installed at " .. entry .. ". Run HyDE's install.sh -r, or point Hyprland at your own config."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
handle:close()
|
||||||
|
dofile(entry)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local hc = hyde.config
|
||||||
|
local home = os.getenv("HOME") or ""
|
||||||
|
|
||||||
|
-- ── Apps ─────────────────────────────────────────────────────────────────────
|
||||||
|
-- Named directly (not hyde-shell open --fall …) for the same reason hyde.conf
|
||||||
|
-- did: skip the wrapper, match the xdg defaults. The assignments cover lazy
|
||||||
|
-- readers of hc.app.*, but key_binds.lua already baked the defaults into its
|
||||||
|
-- Super+B/E dispatchers at bind time — hence the explicit rebinds in
|
||||||
|
-- dots_binds.lua.
|
||||||
|
hc.app.browser = "zen-browser"
|
||||||
|
hc.app.explorer = "dolphin"
|
||||||
|
|
||||||
|
-- ── Startup ──────────────────────────────────────────────────────────────────
|
||||||
|
-- One knob suppresses HyDE's plain waybar AND launches the cava rig (was
|
||||||
|
-- $start.BAR= + a userprefs exec-once). start_up.lua captures hc.start by
|
||||||
|
-- reference and the hyprland.start event fires after this file loads, so the
|
||||||
|
-- override lands in time. `wbar` (gui/.local/bin) runs the rig as the
|
||||||
|
-- dots-waybar.service transient unit with Restart=always: waybar 0.15.0 dies on
|
||||||
|
-- every DP-2 link drop/return, and as a plain scope nothing relaunched it.
|
||||||
|
-- Absolute path: the exec env's PATH is not guaranteed to hold ~/.local/bin.
|
||||||
|
hc.start.bar = home .. "/.local/bin/wbar start"
|
||||||
|
-- Not part of the legacy session — the Lua defaults would newly launch these
|
||||||
|
-- (both binaries are installed). hyprsunset would visibly tint the screen.
|
||||||
|
-- Delete a line to adopt the HyDE default.
|
||||||
|
hc.start.blue_light_filter_daemon = ""
|
||||||
|
hc.start.clipboard_persist = ""
|
||||||
|
-- nm-applet --indicator is already the Lua default (kept deliberately: the
|
||||||
|
-- tray icon is the one place with a quick connect/disconnect menu).
|
||||||
|
|
||||||
|
-- ── UI / icon theme ──────────────────────────────────────────────────────────
|
||||||
|
-- dots-icons = repo-tracked overlay theme (gui/.local/share/icons/dots-icons)
|
||||||
|
-- that inherits Tela-circle-dracula and swaps the tray/status icons for the
|
||||||
|
-- monochrome panel set — without it, waybar on a fractionally-scaled monitor
|
||||||
|
-- gets the colored-circle variants (the blue nm-applet blob).
|
||||||
|
hc.ui.icon_theme = "dots-icons"
|
||||||
|
-- The theme-switch pipeline regenerates lua_state/ui.lua from the theme's
|
||||||
|
-- hypr.theme and writes THAT to dconf (color/hypr.sh -> color/dconf.lua), so
|
||||||
|
-- the assignment above is not enough on its own. Top-level code re-runs on
|
||||||
|
-- every `hyprctl reload`, and both theme-switch paths end in a reload, so this
|
||||||
|
-- write lands last — the Lua-mode equivalent of the legacy $exec.dConf.
|
||||||
|
-- Upstream-sanctioned alternative if this ever races: add
|
||||||
|
-- [hyprland]\n icon_theme = "dots-icons"
|
||||||
|
-- to ~/.config/hyde/config.toml (state/hyde/hyprland.conf then overrides the
|
||||||
|
-- theme vars in theme.switch.sh:151).
|
||||||
|
hl.exec_cmd([[dconf write /org/gnome/desktop/interface/icon-theme "'dots-icons'"]])
|
||||||
|
|
||||||
|
-- ── Env (was userprefs.conf) ─────────────────────────────────────────────────
|
||||||
|
hl.env("SSH_AUTH_SOCK", (os.getenv("XDG_RUNTIME_DIR") or "") .. "/ssh-agent.socket")
|
||||||
|
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "x11") -- disable after hyprland fixes electron flickering
|
||||||
|
|
||||||
|
-- ── Layer + window rules (dots_rules.lua) ────────────────────────────────────
|
||||||
|
-- Plain require, not check_require: stow --no-folding links new files one by
|
||||||
|
-- one, so a host that pulled without re-running install.sh fails loudly in
|
||||||
|
-- `hyprctl configerrors` instead of booting silently without its rules/binds.
|
||||||
|
-- The dots_ prefix matters: hyde.lua searches HyDE's own lua/ before
|
||||||
|
-- ~/.config/hypr, so a bare window_rules / key_binds would load HyDE's module.
|
||||||
|
require("dots_rules")
|
||||||
|
|
||||||
|
-- ── Animations ───────────────────────────────────────────────────────────────
|
||||||
|
-- The legacy chain ran $ANIMATION=theme — the hyprdots set from the theme's
|
||||||
|
-- animations.dcol. The Lua chain cannot inherit it: hyq's hyprland-lua.json
|
||||||
|
-- schema dumps decoration/borders into lua_state/hypr_theme.lua but NOT the
|
||||||
|
-- animations block (LUA-PORT.md risk 6, confirmed live), so without this the
|
||||||
|
-- session falls back to Hyprland's slower built-in defaults. Ported verbatim
|
||||||
|
-- from the generated ~/.config/hypr/animations/theme.conf.
|
||||||
|
-- Conditional so `hyde-shell animations --select` still works: a selector-
|
||||||
|
-- chosen lua_state/animations.lua loads earlier (dynamic.lua) and this block
|
||||||
|
-- steps aside for it.
|
||||||
|
local anim_state = (os.getenv("XDG_STATE_HOME") or home .. "/.local/state") .. "/hyde/lua_state/animations.lua"
|
||||||
|
local anim_f = io.open(anim_state, "r")
|
||||||
|
if anim_f then
|
||||||
|
anim_f:close()
|
||||||
|
else
|
||||||
|
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
|
||||||
|
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
|
||||||
|
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
|
||||||
|
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
|
||||||
|
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
|
||||||
|
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
|
||||||
|
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
|
||||||
|
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
|
||||||
|
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
|
||||||
|
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "once" })
|
||||||
|
hl.animation({ leaf = "fade", enabled = true, speed = 10, bezier = "default" })
|
||||||
|
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ── Keybinds (dots_binds.lua) ────────────────────────────────────────────────
|
||||||
|
-- After the hc.app.* overrides above: the launcher binds read them.
|
||||||
|
require("dots_binds")
|
||||||
|
|
||||||
|
-- ── Host layer ───────────────────────────────────────────────────────────────
|
||||||
|
-- Resolves via hyde.lua's package.path (~/.config/hypr/?.lua); each host
|
||||||
|
-- overlay stows its own host.lua (wm/, lw/). check_require is HyDE's
|
||||||
|
-- missing-tolerant require — a host without one just skips it.
|
||||||
|
check_require("host")
|
||||||
Loaded 100 of 183 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user