[Update] bulk update

This commit is contained in:
2026-09-12 23:38:18 +02:00
parent 3ec2503f38
commit 675ce12b2e
162 changed files with 3348 additions and 2419 deletions
+28
View File
@@ -0,0 +1,28 @@
# Shell formatting for this repo.
#
# Both `shfmt` and the Claude format hook (common/.config/claude/hooks/format.py, which only
# runs shfmt/fish_indent where an .editorconfig opts in) read this file, so the policy and the
# tool that enforces it live in one place. Neovim reads it natively too.
#
# The settings were picked to stay as close as possible to how these scripts were already
# written by hand: 2-space indent, `case` arms indented under `case`, and `&&`/`||` starting
# the wrapped line rather than trailing the one before it (binary_next_line, which reproduces
# the existing `\` continuations exactly). shfmt still rewrites
# a few things no option controls — one-line `case x) a; b ;;` arms and `{ a; b; }` guards
# are split across lines, and `> file` loses its space — which is the one-time cost of
# having a formatter own the layout instead of maintaining it by hand.
#
# `keep_padding` is deliberately NOT set: it is deprecated in shfmt, and it indents the
# statements it splits by the padding column, producing a cascading staircase.
#
# Scoped to shell on purpose — there is no [*] section, so the repo's other filetypes are
# untouched (a [*] block would retune all of them in any editorconfig-aware editor, and
# would also switch on the hook's fish_indent for every fish file).
root = true
[{*.sh,dotsync,dotpub}]
indent_style = space
indent_size = 2
switch_case_indent = true
binary_next_line = true
+4
View File
@@ -32,3 +32,7 @@ __pycache__/
# HyDE writes these at theme-apply time (sourced by animations/theme.conf & hyprlock/theme.conf)
**/hypr/animations/HyDE.conf
**/hypr/hyprlock/HyDE.conf
# 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.
**/waybar/includes/includes.json
+9
View File
@@ -1,3 +1,12 @@
# Search config for THIS repo only (ripgrep / fd / telescope read .ignore).
#
# Everything this repo holds lives in dotfiles and dotdirs — common/.config, gui/.config,
# common/.pi, common/.bashrc — which rg and fd skip by default, so an unqualified search
# here returned nothing at all. Un-hide them, then put back the two dirs that would swamp
# every result: negating .* alone drags in the whole of .git, and .claude/worktrees holds
# entire checkouts of this same repo (background sessions create them), so every hit would
# appear several times over. Scoped to this directory tree; searches elsewhere on the
# machine are unaffected.
!.*
.git/
.claude/
+47 -13
View File
@@ -33,8 +33,28 @@ a host override a single file in a shared dir without shadowing the rest.
**Editing a config edits the repo copy directly** (it's a symlink) — no deploy step. You only re-run
`install.sh` when files are **added or removed** (to create/prune symlinks). See **Syncing** below.
The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention). A host's
package name must match `hostname -s` (or pass the host explicitly, e.g. `./install.sh lw`).
The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention).
### Which host package a machine deploys
**Hostname is not the authority** — the machine remembers its own choice, so several boxes can deploy
the same overlay without sharing a hostname. `install.sh` resolves the host as:
1. the explicit argument (`./install.sh wm`),
2. else the name in **`~/.config/dots-host`** — written by `install.sh` itself on every run that
resolved a real package, so a one-time `./install.sh <host>` is all the setup there is,
3. else `hostname -s`, purely as a convenience guess for boxes where the two happen to agree.
`~/.config/dots-host` is a machine-local, hand-editable one-word file — the sibling of the
`dots-headless` marker above. It is what makes **`bin/dotsync` correct**: dotsync re-links
automatically and has no argument to pass, so without a recorded host it could only guess. Switching
a machine to another overlay is just `./install.sh <other>` — the previously recorded host is
unstowed first, so the base layers don't collide with its leftover links.
Mismatched hostnames used to fail in a way worth recognizing: `./install.sh` on a box whose hostname
matches no package deploys `common`, aborts `gui` on every file the real overlay owns
(`existing target is stowed to a different package`) and skips the overlay entirely — a loud
`exit 1`, but a half-applied one, where newly added files silently get no symlink.
## Install (new machine)
@@ -42,7 +62,7 @@ package name must match `hostname -s` (or pass the host explicitly, e.g. `./inst
sudo pacman -S stow
git clone <remote> ~/.dots
cd ~/.dots
./install.sh # auto-detects host via `hostname -s`
./install.sh wm # name the host package once — it is remembered (see above)
```
`./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert),
@@ -89,15 +109,16 @@ Because configs are symlinks into the repo, **you almost never run `install.sh`
- **Never `stow` the repo root** (`stow .dots`, `stow */`) — it folds package dirs into `~` as stray
`~/common`, `~/gui`, … symlinks. `install.sh` only ever links package *contents*.
- **New host:** create a top-level `<name>/` package (at least a `host.fish`), commit, then
`./install.sh`. If it's terminal-only, skip the package and just `touch ~/.config/dots-headless`
(it runs `common` only).
`./install.sh <name>` (naming it once records it — later runs and dotsync need no argument).
If it's terminal-only, skip the package and just `touch ~/.config/dots-headless` (it runs
`common` only).
## Tooling (`bin/`)
| Command | What it does |
|---|---|
| `./install.sh [host]` | Deploy `common → gui → <host>`. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed; fails loudly (exit 1) if a package couldn't stow. |
| `./bin/dotsync [-n]` | Sync with least churn: `git pull`, re-stow only if files were added/removed. |
| `./install.sh [host]` | Deploy `common → gui → <host>`. Host = arg, else `~/.config/dots-host`, else `hostname -s`; the resolved host is recorded there. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed, a previously recorded host is released before re-deploying; fails loudly (exit 1) if a package couldn't stow. |
| `./bin/dotsync [-n\|host]` | Sync with least churn: `git pull`, re-stow only if files were added/removed (via `install.sh`, using the recorded host). |
| `./bin/reconcile-hyde.sh [-i\|--relink] [host]` | After a HyDE update overwrote `~/.config/hypr`, reconcile HyDE's new files against your dots versions (report / `-i` keep·take·merge each / `--relink` re-symlink). |
## How per-host differences are expressed
@@ -118,9 +139,16 @@ provider also listed so any host can switch) — no per-host overrides.
The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based. `gui/.config/hypr/*` is
your customization layer, but it **sources HyDE's framework**:
`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables and is
**not tracked by dots** (HyDE generates it). So a GUI host must have **HyDE installed**, or Hyprland
fails to start (`source globbing error` + `invalid mod $mainMod`).
`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables. So a GUI
host must have **HyDE installed**, or Hyprland fails to start (`source globbing error` +
`invalid mod $mainMod`).
That file used to be generated-and-untracked, but **HyDE 26.x deploys neither it nor `hyde.conf`**,
so since the 2026-08-14 audit both are **tracked pins** in `gui/.local/share/hyde/` — the one
deliberate exception to the generated-files-are-never-tracked rule below, interim until the Lua port.
After pulling that 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
required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`).
@@ -143,9 +171,15 @@ track neither. HyDE-managed customization goes via:
self-contained two-bar config launched raw (`waybar -c …custom.jsonc -s …custom.css` — exec-once,
the Ctrl+Alt+W / Super+Shift+R binds, and the `wbar` abbr). **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 (systemd
user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service`, auto-restarting) must be disabled once:
`systemctl --user stop <unit>; systemctl --user mask <unit>`.
from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar must be
disabled once, or **two bars appear at every login** — and the launcher differs by HyDE vintage:
- current HyDE: systemd user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service` (auto-restarting) —
`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`
— neutralized by `$start.BAR=` in `gui/.config/hypr/hyde.conf` (empty = unset; hyde.conf is
sourced before that exec-once). Current HyDE's `variables.conf` re-sets `$start.BAR`, so that
line stops working after an update — see HYDE-UPDATE.md step 4.
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).
+42 -10
View File
@@ -9,18 +9,27 @@
#
# dotsync pull, then relink only if the file set changed
# dotsync -n preview incoming changes (fetch only; no pull, no relink)
# dotsync <host> as above, forcing the host package (normally unnecessary: install.sh
# reuses the host recorded in ~/.config/dots-host)
set -uo pipefail
DOTS="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)"
cd "$DOTS" || exit 1
if [ "${1:-}" = "-n" ] || [ "${1:-}" = "--dry-run" ]; then
git fetch -q 2>/dev/null || { echo "fetch failed (offline?)"; exit 1; }
up="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null)" || { echo "no upstream set"; exit 1; }
git fetch -q 2>/dev/null || {
echo "fetch failed (offline?)"
exit 1
}
up="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null)" || {
echo "no upstream set"
exit 1
}
# rev-list, not diff: a plain diff also fires when the LOCAL side is ahead, mislabelling
# your own unpushed commits as incoming. Count commits that exist only on the upstream.
n="$(git rev-list --count "HEAD..$up")"
if [ "$n" -eq 0 ]; then echo "already up to date with $up."; else
echo "incoming from $up ($n commit(s)):"; git --no-pager diff --stat "HEAD...$up"
echo "incoming from $up ($n commit(s)):"
git --no-pager diff --stat "HEAD...$up"
fi
exit 0
fi
@@ -40,7 +49,7 @@ fi
# Services that read config ONCE at startup don't pick up pulled edits — nudge loudly.
# (Symlinked edits are live for everything else; these are the exceptions that bit us.)
if git diff --name-only "$before" "$after" | grep -q '\.config/llamacpp/' \
&& systemctl cat llama.service &>/dev/null; then
&& systemctl cat llama.service &>/dev/null; then
echo ""
echo "⚠⚠ llamacpp presets changed — the router reads them only at startup. Apply with:"
echo " sudo systemctl restart llama.service"
@@ -49,18 +58,41 @@ fi
# A unit file is read from /etc, not through the stow symlink — needs daemon-reload as well.
if git diff --name-only "$before" "$after" | grep -q '\.config/whisper/' \
&& systemctl cat whisper.service &>/dev/null; then
&& systemctl cat whisper.service &>/dev/null; then
echo ""
echo "⚠⚠ whisper unit changed — apply with:"
echo " sudo systemctl daemon-reload && sudo systemctl restart whisper.service"
echo ""
fi
# Modified files (M) are already live through the symlinks. Only Added/Deleted/Renamed
# need (un)linking, which is the only case that justifies the churny re-stow.
if git diff --name-status "$before" "$after" | grep -qE '^(A|D|R)'; then
echo "files added/removed/renamed — re-linking via install.sh (this may reload some apps)…"
exec "$DOTS/install.sh"
# Modified files (M) are live only through INTACT symlinks — a live file severed by an
# atomic rewrite (the recurring claude/settings.json class) swallows pulled edits silently,
# so verify that premise per modified file instead of assuming it. Added/Deleted/Renamed
# always need (un)linking. The diff is captured once, not piped into `grep -q`: its early
# exit can SIGPIPE git under pipefail and turn the whole condition falsely negative.
changes="$(git diff --name-status "$before" "$after")"
need_restow=""
if printf '%s\n' "$changes" | grep -qE '^(A|D|R)'; then
need_restow="files added/removed/renamed"
else
# map each modified repo path (package/.dotpath) to its live counterpart; a real file
# (not a symlink) sitting there means the pulled edit did NOT land
while IFS= read -r rel; do
live="$HOME/$rel"
if [ -e "$live" ] && [ ! -L "$live" ]; then
echo "note: ~/$rel is a real file, not a symlink into the repo — the pulled edit has NOT landed there."
need_restow="severed symlink(s)"
fi
done < <(printf '%s\n' "$changes" | awk -F'\t' '$1 ~ /^M/ && $2 ~ /^[^\/]+\/\./ { sub(/^[^\/]+\//, "", $2); print $2 }')
fi
if [ -n "$need_restow" ]; then
echo "$need_restow — re-linking via install.sh (this may reload some apps)…"
# Normally called with no argument, so install.sh resolves the host from ~/.config/dots-host —
# the one recorded by the last install.sh run here. That is exactly why the host must not be
# derived from `hostname -s`: this path is automatic and has nothing to pass. An explicit
# `dotsync <host>` is forwarded through for the rare override.
exec "$DOTS/install.sh" "$@"
else
echo "edits only — already live through the symlinks; no re-stow, no reload needed."
fi
+38
View File
@@ -38,6 +38,44 @@ alias gl='git log --graph --show-signature'
alias gla="git log --all --decorate --oneline --graph"
alias gm='git merge'
alias exti=exit
alias cat="bat -p"
alias untar="tar -xf"
alias keybinds="cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf"
alias dmz="cat ~/.config/fish/dmz.txt"
alias nmatrix="neo-matrix -DS 3"
alias tts="tt -notheme -bold -showwpm -json"
alias tuioss="tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit"
alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim"
alias gcommit="git diff HEAD | aichat -r commit"
alias aichatdel="rm ~/.config/aichat/sessions/*.yaml"
# Network
alias vpnhome="sudo wg-quick up wg0"
alias vpnkralizec="sudo wg-quick up kralizec-wg0"
alias vpnsumadija="sudo wg-quick up sumadija-wg0"
# scrcpy
alias scrcpyc='scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close'
alias scrcpys='scrcpy -wS --power-off-on-close'
alias scrcpyz='scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio'
# `ls` must be aliased to eza or `lt` (above) falls through to coreutils ls, which has
# no --tree. bash re-expands the first word of an alias, so l/la/lla/lt all pick up eza.
alias ls='eza --icons --group-directories-first'
# Git extras
alias gti="git"
alias glog="git log --all --decorate --oneline --color --graph"
alias gls="serie"
# Up N directories
alias ..="cd .."
alias ...="cd ../.."
alias .3="cd ../../.."
alias .4="cd ../../../.."
alias .5="cd ../../../../.."
# Powerline prompt, synthwave palette (needs a Nerd Font for the glyphs)
PROMPT_DIRTRIM=3
__pl_sep=$'\ue0b0' # powerline triangle
+3
View File
@@ -35,6 +35,9 @@ clients:
- name: Qwen3.5-9B-UD-Q6_K_XL
max_input_tokens: 30000 # ctx 32768
supports_vision: true
- name: Qwen3.8-27B-UD-IQ3_XXS
max_input_tokens: 22000 # ctx 24576
supports_vision: true
- name: gemma-4-26B-A4B-it-UD-IQ4_XS
max_input_tokens: 22000 # ctx 24576
supports_vision: true
+3 -3
View File
@@ -8,13 +8,13 @@ resize), trailing (lowest-priority) segments are dropped from the right until it
Wide — everything:
```
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 💾 81%
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 💾 81%
```
Narrow — trailing segments trimmed:
```
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h
```
Every segment is defensive: if its data is missing or a command fails, the segment is
@@ -29,7 +29,7 @@ is supplementary to the main colored value.
| 📁 | **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 |
| 🤖 | **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 | window = 1M for `[1m]` models, else 200k. Adds a red **⚠compact** at ≥80% |
| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | window comes from the payload's `context_window.context_window_size`, falling back to `(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 |
| 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s |
| 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` |
+68 -9
View File
@@ -3,17 +3,19 @@
Reads the hook JSON from stdin, extracts the edited file path, and runs a
language-appropriate formatter -- but, to avoid imposing a style on a project
that never asked for it, opinionated formatters (stylua, ruff, prettier, taplo)
only run when the project opts in via a config file discoverable by walking up
from the edited file. Canonical single-style toolchains run on sight: gofmt and
rustfmt on their go.mod / Cargo.toml marker; fish_indent and shfmt whenever the
tool itself is installed (shell and fish have one de-facto style, no config).
that never asked for it, every formatter here only runs when the project opts in
via a config file discoverable by walking up from the edited file: stylua, ruff,
prettier and taplo on their own configs, gofmt and rustfmt on go.mod / Cargo.toml,
shfmt and fish_indent on .editorconfig. No marker, no formatting -- a repo that
hand-formats its shell keeps its own layout.
Best-effort: always exits 0 so a format hiccup never blocks an edit. jq is not
guaranteed on the host, hence python for the stdin JSON parse.
"""
import fnmatch
import json
import os
import re
import shutil
import subprocess
import sys
@@ -31,6 +33,47 @@ def find_up(start, names):
d = parent
def _expand_braces(pat):
"""`a{b,c}` -> [`ab`, `ac`] (editorconfig sections use {,} alternation)."""
m = re.search(r"\{([^{}]*)\}", pat)
if not m:
return [pat]
out = []
for alt in m.group(1).split(","):
out.extend(_expand_braces(pat[: m.start()] + alt + pat[m.end():]))
return out
def editorconfig_covers(path):
"""True if the nearest ancestor .editorconfig has a section matching `path`.
Mere existence is NOT opt-in: this repo's .editorconfig is deliberately scoped
to `[{*.sh,dotsync,dotpub}]` with no [*] block precisely so fish files stay
untouched, and a generic upstream .editorconfig must not switch a formatter on
for filetypes it never mentions. Patterns without a `/` match the basename
(the editorconfig convention); ones with a `/` match the path relative to the
.editorconfig's directory.
"""
d = find_up(path, [".editorconfig"])
if not d:
return False
name = os.path.basename(path)
rel = os.path.relpath(os.path.abspath(path), d)
try:
with open(os.path.join(d, ".editorconfig"), errors="ignore") as fh:
for line in fh:
line = line.strip()
if not (line.startswith("[") and line.endswith("]")):
continue
for pat in _expand_braces(line[1:-1]):
target = rel if "/" in pat else name
if fnmatch.fnmatch(target, pat.lstrip("/")):
return True
except Exception:
pass
return False
def has_toml_section(start, filename, section):
"""True if the nearest ancestor `filename` contains `section`."""
d = find_up(start, [filename])
@@ -89,14 +132,26 @@ def rustfmt_cmd(path):
return None
# Shell and fish are opt-in too, gated on an .editorconfig SECTION that matches the file
# (shfmt reads .editorconfig natively for its settings, so the marker and the tool agree).
# "One canonical style" turned out not to mean "the style the project uses": 3 of ~/.dots's
# 25 fish files are not fish_indent-clean, so running these on sight rewrote whole files and
# buried a 40-line change in a 200-line diff. ~/.dots opts its shell in via
# `[{*.sh,dotsync,dotpub}]` while its fish files stay hand-formatted — existence of the
# .editorconfig alone must not turn either formatter on.
# Neovim agrees: conform.nvim there lists lua/css/html/ts/js only — never sh or fish.
def fish_cmd(path):
exe = shutil.which("fish_indent") # ships with fish, one canonical style
return [exe, "-w", path] if exe else None
exe = shutil.which("fish_indent")
if exe and editorconfig_covers(path):
return [exe, "-w", path]
return None
def shfmt_cmd(path):
exe = shutil.which("shfmt") # de-facto shell formatter, sane defaults
return [exe, "-w", path] if exe else None
exe = shutil.which("shfmt")
if exe and editorconfig_covers(path):
return [exe, "-w", path]
return None
def taplo_cmd(path):
@@ -133,6 +188,10 @@ def main():
path = ti.get("file_path") or tr.get("filePath") or ""
if not path or not os.path.isfile(path):
return
# Resolve symlinks before walking for markers: deployed dotfiles get edited via
# their ~/.config/... links, but the opt-in configs live beside the real file in
# the repo — a lexical walk from the link's path would never find them.
path = os.path.realpath(path)
handler = HANDLERS.get(os.path.splitext(path)[1].lower())
if not handler:
return
+6
View File
@@ -2,6 +2,12 @@
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"$docs": "https://code.claude.com/docs/en/keybindings",
"bindings": [
{
"context": "Chat",
"bindings": {
"shift+enter": "chat:newline"
}
},
{
"context": "Global",
"bindings": {
+19 -12
View File
@@ -25,8 +25,11 @@ repo=$(dirname "$(readlink -f "$SRC/link.sh")")
# If link.sh itself has broken out of stow into a plain file, $repo resolves to $SRC and
# the drift heal below would relink each drifted file onto ITSELF — destroying the only
# copy of its content. Refuse to continue.
if [ "$repo" = "$SRC" ]; then
# copy of its content. Refuse to continue. Both sides are canonicalized before comparing:
# $repo comes back from readlink -f, so a symlinked component anywhere in the path (say
# /home -> /var/home) would otherwise make the two spellings differ and slip the guard.
src_real=$(readlink -f "$SRC" || printf '%s' "$SRC")
if [ "$repo" = "$src_real" ]; then
echo "error: link.sh itself is a plain file (broken out of stow) — re-stow first:" >&2
echo " cd ~/.dots && ./install.sh" >&2
exit 1
@@ -48,11 +51,13 @@ for item in $items; do
if [ -f "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
same=0
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$DST/$item" "$SRC/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$DST/$item" "$SRC/$item" && same=1 ;;
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$DST/$item" "$SRC/$item" 2>/dev/null && same=1
;;
*)
cmp -s "$DST/$item" "$SRC/$item" && same=1
;;
esac
if [ "$same" != 1 ]; then
echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2
@@ -78,11 +83,13 @@ for item in $items; do
same=0
if [ -f "$repo/$item" ]; then
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$SRC/$item" "$repo/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$SRC/$item" "$repo/$item" && same=1 ;;
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$SRC/$item" "$repo/$item" 2>/dev/null && same=1
;;
*)
cmp -s "$SRC/$item" "$repo/$item" && same=1
;;
esac
fi
if [ "$same" = 1 ]; then
+42 -14
View File
@@ -34,19 +34,7 @@
],
"defaultMode": "auto"
},
"model": "opus[1m]",
"enableWorkflows": false,
"skipWorkflowUsageWarning": true,
"enabledPlugins": {
"typescript-lsp@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true
},
"statusLine": {
"type": "command",
"command": "/usr/bin/env python3 ~/.claude/statusline.py",
"padding": 0,
"hideVimModeIndicator": true
},
"model": "claude-fable-5[1m]",
"hooks": {
"PostToolUse": [
{
@@ -65,9 +53,21 @@
"worktree": {
"baseRef": "fresh"
},
"enableWorkflows": false,
"statusLine": {
"type": "command",
"command": "/usr/bin/env python3 ~/.claude/statusline.py",
"padding": 0,
"hideVimModeIndicator": true
},
"enabledPlugins": {
"typescript-lsp@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true
},
"effortLevel": "xhigh",
"advisorModel": "fable",
"tui": "fullscreen",
"skipWorkflowUsageWarning": true,
"theme": "dark",
"editorMode": "vim",
"verbose": true,
@@ -75,5 +75,33 @@
"remoteControlAtStartup": false,
"inputNeededNotifEnabled": false,
"agentPushNotifEnabled": false,
"skipAutoPermissionPrompt": true
"skipAutoPermissionPrompt": true,
"autoMode": {
"environment": [
"### Org-wide",
"**Organization**: None configured",
"**Cloud provider(s)**: None configured",
"**Repository visibility**: private (wingman-ai/c2, via gh)",
"**Internal sharing / snippet hosting**: None configured — treat public paste/gist services as outside the trust boundary",
"**Secrets management**: None configured",
"**Default / protected branches**: Default branch master; protected branches: none listed (rulesets not queryable here — do not assume unprotected)",
"**CI/CD deploy targets**: None configured",
"**Network posture**: None configured",
"**Source control**: The trusted repo (wingman-ai/c2, origin github.com:wingman-ai/c2.git) and its remote(s) only",
"**Trusted internal domains**: None configured",
"**Trusted cloud buckets**: None configured",
"**Key internal services**: None configured",
"**Internal package registry**: None configured",
"**Sensitive data locations & audiences**: any file or store holding personal data, confidential business data, credentials, regulated data, or similarly sensitive material; preserve exact handles when known and share only with audiences cleared at the [named+specifics] bar; note .env, .env.dev, .env.example are gitignored/tracked as sensitive-looking paths in this repo",
"**Data retention / declassification**: None configured",
"**Sensitive remote targets**: any namespace, host, or container whose name carries `prod` or `production` as a whole word or name segment",
"**Protected deployment namespaces / environments**: None configured — fall back to the Sensitive remote targets heuristic",
"**Protected IaC scopes**: IAM, RBAC, networking, quota, and node-pool resources; anything whose name or tag carries `prod` or `production` as a whole word or name segment",
"### User-specific",
"**Primary use of Claude Code**: software development (Electron/Vite/React/TypeScript UAV client controller, wm-drone-controller / c2-building)",
"**Trusted repo**: /home/coja/projects/wingman/wm-clients/c2/c2-building (origin github.com:wingman-ai/c2.git), private — confidential material is fine here; content ported from outside this session's repo is not this repo's own work",
"**Org-specific CLIs**: None configured",
"routine under this repo's Makefile/pnpm targets (make all, make prepare, make fnm-node, pnpm scripts) is expected local development activity"
]
}
}
+66 -17
View File
@@ -90,25 +90,49 @@ def term_cols():
# --- shared transcript usage ------------------------------------------------
def _scan_usage(text):
"""Last `message.usage` block among these JSONL lines, or {}."""
usage = {}
for line in text.splitlines():
line = line.strip()
if not line or '"usage"' not in line:
continue
try:
obj = json.loads(line)
except Exception:
continue
msg = obj.get("message")
if isinstance(msg, dict) and isinstance(msg.get("usage"), dict):
usage = msg["usage"]
return usage
TRANSCRIPT_TAIL = 1 << 20 # 1 MiB
def last_usage(data):
"""Most recent `message.usage` block from the transcript, or {}."""
"""Most recent `message.usage` block from the transcript, or {}.
Reads only the tail: this runs on every status refresh and transcripts grow to
tens of MB, so re-parsing the whole file each time is unbounded work for a value
that lives at the end. Falls back to a full scan if the tail holds no usage block
(a long stretch of tool results can push it past the window).
"""
try:
path = data.get("transcript_path")
if not path or not os.path.exists(path):
return {}
usage = {}
with open(path, "r", errors="ignore") as fh:
for line in fh:
line = line.strip()
if not line or '"usage"' not in line:
continue
try:
obj = json.loads(line)
except Exception:
continue
msg = obj.get("message")
if isinstance(msg, dict) and isinstance(msg.get("usage"), dict):
usage = msg["usage"]
with open(path, "rb") as fh:
size = fh.seek(0, os.SEEK_END)
start = max(0, size - TRANSCRIPT_TAIL)
fh.seek(start)
text = fh.read().decode("utf-8", "ignore")
if start:
text = text.partition("\n")[2] # drop the partial first line
usage = _scan_usage(text)
if not usage and start:
with open(path, "r", errors="ignore") as fh:
usage = _scan_usage(fh.read())
return usage
except Exception:
return {}
@@ -193,7 +217,14 @@ def git_segment(cwd, top):
return ""
repo = os.path.basename(top) or "repo"
branch = _git(cwd, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip() or "?"
dirty = bool(_git(cwd, "status", "--porcelain").stdout.strip())
# `status --porcelain` walks the whole work tree, so in a big repo it can blow
# the 1s timeout -- in its own try, because letting that escape dropped the repo
# and branch we already had: a decoration failing must not take the segment down.
try:
dirty = bool(_git(cwd, "status", "--porcelain").stdout.strip())
except Exception:
dirty = False
# ahead/behind vs upstream (omitted when no upstream is configured)
ab = ""
@@ -231,6 +262,25 @@ def model_segment(data):
return out
def context_window(data):
"""Size of this session's context window, in tokens.
Claude Code resolves the `[1m]` model suffix away before handing the status line
its JSON -- `model.id` is always the bare API id ("claude-fable-5") -- so sniffing
the id for "1m" could never detect a 1M session and every one of them was measured
against a 200k window (5x overstated %, ⚠compact from ~16% real usage). The payload
carries the real number; display_name ("Opus 4.8 (1M context)") is the fallback.
"""
cw = data.get("context_window")
if isinstance(cw, dict):
for key in ("context_window_size", "max_tokens", "size"):
val = cw.get(key)
if isinstance(val, (int, float)) and val > 0:
return int(val)
name = ((data.get("model") or {}).get("display_name") or "").lower()
return 1_000_000 if "1m" in name else 200_000
def context_segment(data, usage):
try:
if not usage:
@@ -240,8 +290,7 @@ def context_segment(data, usage):
+ usage.get("cache_creation_input_tokens", 0)
+ usage.get("cache_read_input_tokens", 0)
)
model_id = (data.get("model") or {}).get("id", "")
window = 1_000_000 if "1m" in model_id.lower() else 200_000
window = context_window(data)
pct = used / window * 100 if window else 0
col = bucket(pct, 50, 80)
out = "📝 " + color(f"{pct:.0f}%", col) + color(f" ({used / 1000:.0f}k)", "gray", dim=True)
@@ -1,4 +1,3 @@
function __fish_pipx_complete
set -x _ARGCOMPLETE 1
set -x _ARGCOMPLETE_DFS \t
+97 -87
View File
@@ -14,100 +14,110 @@ set -gx FZF_DEFAULT_COMMAND 'fd --type f --strip-cwd-prefix'
set -gx NEWT_COLORS 'root=black,black;window=black,black;border=white,black;listbox=white,black;label=blue,black;checkbox=red,black;title=green,black;button=white,red;actsellistbox=white,red;actlistbox=white,gray;compactbutton=white,gray;actcheckbox=white,blue;entry=lightgray,black;textbox=blue,black' # themes nmtui
# set -gx BAT_THEME "Catppuccin Mocha"
# Cursor / greeting
set -g fish_greeting
set -g fish_cursor_insert line
set -g fish_cursor_default block
set -g fish_cursor_visual underscore
# Misc
alias exti=exit
alias dmz="cat ~/.config/fish/dmz.txt"
alias nmatrix="neo-matrix -DS 3"
alias grep="grep --color=auto"
alias fzf="fzf --preview 'bat --color=always {}'"
alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim"
abbr scrcpyz 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio'
# Git
alias gti="git"
alias gs="git status -s"
alias gca="git add -p . && git commit"
alias gd="git diff --word-diff"
alias gl="git log --graph --show-signature"
alias glog="git log --all --decorate --oneline --color --graph"
alias gla="git log --all --decorate --oneline"
alias gls="serie"
alias gm="git merge"
# List directory
alias ls='eza --icons --group-directories-first'
alias l="ls -l"
alias la="ls -a"
alias lla="ls -la"
alias lt="ls --tree"
# Dotfiles + notes
alias cdots="cd ~/.dots/"
alias cpnotes="cd ~/sync/PersonalNotes/"
alias cnotes="cd ~/sync/CojaDuska/Notes/"
abbr notes "cnotes && nvim"
abbr dots "cdots && nvim"
abbr dotsync "cdots && ./bin/dotsync"
abbr pnotes "cpnotes && nvim"
abbr todo "cpnotes && nvim ToDoNext.md"
abbr aliases "bat ~/.config/fish/config.fish"
# Handy
abbr cat "bat -p"
abbr untar "tar -xf"
abbr unarchive "atool -x"
abbr copy wl-copy
abbr img "kitten icat"
abbr fm yazi
abbr lg lazygit
abbr gcommit "git diff HEAD | aichat -r commit"
abbr aichatdel "rm ~/.config/aichat/sessions/*.yaml"
abbr mkdir "mkdir -p"
abbr faillock "sudo faillock --reset"
abbr xremaps "sudo xremap ~/.config/xremap/config.yml"
abbr pacs "sudo pacman -Syu --noconfirm"
abbr yays "yay --noconfirm --sudoloop"
abbr nmaps "sudo nmap -sn 192.168.0.0/24"
abbr scrcpyc 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close'
abbr scrcpys 'scrcpy -wS --power-off-on-close'
abbr tts "tt -notheme -bold -showwpm -json"
abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit"
abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf"
abbr vpnhome "sudo wg-quick up wg0"
abbr vpnkralizec "sudo wg-quick up kralizec-wg0"
abbr vpnsumadija "sudo wg-quick up sumadija-wg0"
abbr ipadd "sudo ip route add 192.168.0.234 dev wg0"
abbr aria2cs "aria2c -x 16 -k 1M"
# Change-dir shortcuts
abbr .. "cd .."
abbr ... "cd ../.."
abbr .3 "cd ../../.."
abbr .4 "cd ../../../.."
abbr .5 "cd ../../../../.."
# Tool init (fnm is initialized once in conf.d/fnm.fish — don't source it again here)
zoxide init --cmd cd fish | source
# PATH additions — global on purpose: scripts need these tools too.
# (fnm is initialized once in conf.d/fnm.fish — don't source it again here)
fish_add_path $HOME/.cargo/bin $HOME/.local/bin
# thefuck, lazy-loaded on first use (its --alias eval costs ~170ms of shell startup)
function fuck
functions -e fuck
thefuck --alias | source
fuck $argv
end
# pnpm
set -gx PNPM_HOME "$HOME/.local/share/pnpm"
if not string match -q -- $PNPM_HOME $PATH
set -gx PATH "$PNPM_HOME" $PATH
end
# Everything below is for a human at a prompt. config.fish is sourced by EVERY fish —
# scripts and `fish -c` included — and zoxide's `--cmd cd` alias is unconditional: in a
# script, a failed `cd` would silently land in zoxide's best database match (exit 0)
# instead of erroring, and every script `cd` would pollute the database. Aliases/abbrs
# leak into scripts the same way (`ls` → eza, `mkdir` → `mkdir -p`, fzf previews …).
if status is-interactive
# Cursor / greeting
set -g fish_greeting
set -g fish_cursor_insert line
set -g fish_cursor_default block
set -g fish_cursor_visual underscore
# Misc
alias exti=exit
alias dmz="cat ~/.config/fish/dmz.txt"
alias nmatrix="neo-matrix -DS 3"
alias grep="grep --color=auto"
alias fzf="fzf --preview 'bat --color=always {}'"
alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim"
abbr scrcpyz 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio'
# Git
alias gti="git"
alias gs="git status -s"
alias gca="git add -p . && git commit"
alias gd="git diff --word-diff"
alias gl="git log --graph --show-signature"
alias glog="git log --all --decorate --oneline --color --graph"
alias gla="git log --all --decorate --oneline"
alias gls="serie"
alias gm="git merge"
# List directory
alias ls='eza --icons --group-directories-first'
alias l="ls -l"
alias la="ls -a"
alias lla="ls -la"
alias lt="ls --tree"
# Dotfiles + notes
alias cdots="cd ~/.dots/"
alias cpnotes="cd ~/sync/PersonalNotes/"
alias cnotes="cd ~/sync/CojaDuska/Notes/"
abbr notes "cnotes && nvim"
abbr dots "cdots && nvim"
abbr dotsync "cdots && ./bin/dotsync"
abbr pnotes "cpnotes && nvim"
abbr todo "cpnotes && nvim ToDoNext.md"
abbr aliases "bat ~/.config/fish/config.fish"
# Handy
abbr cat "bat -p"
abbr untar "tar -xf"
abbr unarchive "atool -x"
abbr copy wl-copy
abbr img "kitten icat"
abbr fm yazi
abbr lg lazygit
abbr gcommit "git diff HEAD | aichat -r commit"
abbr aichatdel "rm ~/.config/aichat/sessions/*.yaml"
abbr mkdir "mkdir -p"
abbr faillock "sudo faillock --reset"
abbr xremaps "sudo xremap ~/.config/xremap/config.yml"
abbr pacs "sudo pacman -Syu --noconfirm"
abbr yays "yay --noconfirm --sudoloop"
abbr nmaps "sudo nmap -sn 192.168.0.0/24"
abbr scrcpyc 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close'
abbr scrcpys 'scrcpy -wS --power-off-on-close'
abbr tts "tt -notheme -bold -showwpm -json"
abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit"
abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf"
abbr vpnhome "sudo wg-quick up wg0"
abbr vpnkralizec "sudo wg-quick up kralizec-wg0"
abbr vpnsumadija "sudo wg-quick up sumadija-wg0"
abbr ipadd "sudo ip route add 192.168.0.234 dev wg0"
abbr aria2cs "aria2c -x 16 -k 1M"
# Change-dir shortcuts
abbr .. "cd .."
abbr ... "cd ../.."
abbr .3 "cd ../../.."
abbr .4 "cd ../../../.."
abbr .5 "cd ../../../../.."
# Tool init
zoxide init --cmd cd fish | source
# thefuck, lazy-loaded on first use (its --alias eval costs ~170ms of shell startup)
function fuck
functions -e fuck
thefuck --alias | source
fuck $argv
end
end
# Per-host extras
test -f ~/.config/fish/host.fish; and source ~/.config/fish/host.fish
@@ -1,23 +1,25 @@
function __ssh_agent_is_started -d "check if ssh agent is already started"
if test -n "$SSH_CONNECTION"
# This is an SSH session
ssh-add -l > /dev/null 2>&1
if test $status -eq 0 -o $status -eq 1
# An SSH agent was forwarded
return 0
end
end
if test -n "$SSH_CONNECTION"
# This is an SSH session
ssh-add -l >/dev/null 2>&1
if test $status -eq 0 -o $status -eq 1
# An SSH agent was forwarded
return 0
end
end
if begin; test -f "$SSH_ENV"; and test -z "$SSH_AGENT_PID"; end
source $SSH_ENV > /dev/null
end
if begin
test -f "$SSH_ENV"; and test -z "$SSH_AGENT_PID"
end
source $SSH_ENV >/dev/null
end
if test -z "$SSH_AGENT_PID"
return 1
end
if test -z "$SSH_AGENT_PID"
return 1
end
ssh-add -l > /dev/null 2>&1
if test $status -eq 2
return 1
end
ssh-add -l >/dev/null 2>&1
if test $status -eq 2
return 1
end
end
@@ -1,5 +1,5 @@
function __ssh_agent_start -d "start a new ssh agent"
ssh-agent -c | sed 's/^echo/#echo/' > $SSH_ENV
chmod 600 $SSH_ENV
source $SSH_ENV > /dev/null
ssh-agent -c | sed 's/^echo/#echo/' >$SSH_ENV
chmod 600 $SSH_ENV
source $SSH_ENV >/dev/null
end
+2 -2
View File
@@ -1,3 +1,3 @@
function ask --description 'answer from cht.sh'
curl -s https://cht.sh/$(string join '+' $argv[1..])
function ask --description 'answer from cht.sh'
curl -s https://cht.sh/$(string join '+' $argv[1..])
end
@@ -15,4 +15,3 @@ function bind_M_n_history
end
end
end
+5 -1
View File
@@ -1,3 +1,7 @@
function clis --description 'fzf select cli'
eval (cat $HOME/.config/misc/clis.txt | command fzf --multi)
# one eval per selection — a single eval of the joined lines would run the
# first command with the other selections as its arguments
for cmd in (cat $HOME/.config/misc/clis.txt | command fzf --multi)
eval $cmd
end
end
@@ -8,7 +8,7 @@ function copy-commandline
else if type -q wl-copy
echo -n "$cmd" | wl-copy
end
# Provide brief visual feedback in the prompt
set -l original_cmd (commandline)
commandline -r "Copied!"
+4 -2
View File
@@ -7,8 +7,10 @@ function kp
read -s -P "Enter db password: " pass
echo ""
set -l entry (echo $pass | keepassxc-cli ls -q -R $KP_DB | fzf)
# -f flattens to full paths; without it nested entries are printed indented and
# without their group prefix, so `clip` can't find them. Group lines end in "/".
set -l entry (printf '%s\n' $pass | keepassxc-cli ls -q -R -f $KP_DB | string match -v '*/' | fzf | string trim)
if test -n "$entry"
echo $pass | keepassxc-cli clip -q $KP_DB $entry 0
printf '%s\n' $pass | keepassxc-cli clip -q $KP_DB $entry 0
end
end
+15 -15
View File
@@ -1,18 +1,18 @@
function lk --description 'linux knowledge'
set -l DB "$HOME/projects/dmz/soft/lk/db.rec"
set -l passes 0
set -l count 0
set -l query ""
function lk --description 'linux knowledge'
set -l DB "$HOME/projects/dmz/soft/lk/db.rec"
set -l passes 0
set -l count 0
set -l query ""
while test $passes -lt 2; and test $count -eq 0
set query (recsel "$DB" -p aim,tag | recsel -iq "$query" -CP aim,tag | sort -u | fzf --preview="recsel \"$DB\" -e \"aim~{}\" | bat")
if test -n "$query"
set count (recsel "$DB" -q "$query" -c)
end
set passes (math $passes + 1)
end
while test $passes -lt 2; and test $count -eq 0
set query (recsel "$DB" -p aim,tag | recsel -iq "$query" -CP aim,tag | sort -u | fzf --preview="recsel \"$DB\" -e \"aim~{}\" | bat")
if test -n "$query"
set count (recsel "$DB" -q "$query" -c)
end
set passes (math $passes + 1)
end
if test $count -eq 1
recsel "$DB" -q "$query" | recfmt -f "$HOME/projects/dmz/soft/lk/lists.fmt" | less
end
if test $count -eq 1
recsel "$DB" -q "$query" | recfmt -f "$HOME/projects/dmz/soft/lk/lists.fmt" | less
end
end
+20 -23
View File
@@ -1,24 +1,21 @@
function opencodes --description 'contained opencode'
bwrap \
--unshare-all \
--new-session \
--clearenv \
--die-with-parent \
--share-net \
--dev /dev \
--proc /proc \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--ro-bind /bin /bin \
--ro-bind /etc /etc \
--ro-bind ~/.config/opencode ~/.config/opencode \
--tmpfs /tmp/node-compile-cache \
--bind ~/.local/state/opencode ~/.local/state/opencode \
--bind ~/.local/share/opencode ~/.local/share/opencode \
--bind "$(pwd)" "$(pwd)" \
opencode
function opencodes --description 'contained opencode'
bwrap \
--unshare-all \
--new-session \
--clearenv \
--die-with-parent \
--share-net \
--dev /dev \
--proc /proc \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /lib64 /lib64 \
--ro-bind /bin /bin \
--ro-bind /etc /etc \
--ro-bind ~/.config/opencode ~/.config/opencode \
--tmpfs /tmp/node-compile-cache \
--bind ~/.local/state/opencode ~/.local/state/opencode \
--bind ~/.local/share/opencode ~/.local/share/opencode \
--bind "$(pwd)" "$(pwd)" \
opencode
end
+5 -1
View File
@@ -1,3 +1,7 @@
function tuis --description 'fzf select tui'
eval (cat $HOME/.config/misc/tuis.txt | command fzf --multi)
# one eval per selection — a single eval of the joined lines would run the
# first command with the other selections as its arguments
for cmd in (cat $HOME/.config/misc/tuis.txt | command fzf --multi)
eval $cmd
end
end
+1
View File
@@ -40,6 +40,7 @@ asciiquarium
cbonsai
tclock
hunk
tuicr
gittop
lazygit
lazyjournal
@@ -3,6 +3,25 @@ require("nvchad.configs.lspconfig").defaults()
local servers = { "html", "cssls", "ts_ls", "eslint" }
vim.lsp.enable(servers)
-- Waybar/GTK stylesheets are GTK CSS, a different dialect: `@name` references
-- @define-color colors (waybar's theme.css) and functions like alpha() are
-- valid there but not in web CSS, so cssls floods "property value expected" /
-- "{ expected" false errors. No LSP speaks the GTK dialect, so keep cssls
-- attached (standard-property completion still useful) and mute diagnostics
-- on those buffers only.
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if not client or client.name ~= "cssls" then
return
end
local path = vim.api.nvim_buf_get_name(args.buf)
if path:match "/waybar/" or path:match "/gtk%-%d" then
vim.diagnostic.enable(false, { bufnr = args.buf })
end
end,
})
-- local lspconfig = require "lspconfig"
-- local nvlsp = require "nvchad.configs.lspconfig"
--
+20
View File
@@ -34,6 +34,26 @@ return {
"regex",
},
},
-- On the main branch nothing acts on `ensure_installed` at startup: setup() takes only
-- install_dir, and NvChad's :TSInstallAll (which does read this list off the lazy spec)
-- only runs from `build`, i.e. when the plugin is installed or updated. So a language
-- added here stayed uninstalled until the next rebuild — 9 of these 15 were missing,
-- silently falling back to regex highlighting. Install whatever is absent, once, here.
config = function(_, opts)
local ts = require "nvim-treesitter"
ts.setup()
local have = {}
for _, lang in ipairs(ts.get_installed()) do
have[lang] = true
end
local missing = vim.tbl_filter(function(lang)
return not have[lang]
end, opts.ensure_installed or {})
if #missing > 0 then
ts.install(missing) -- async; highlighting picks each up as it lands
end
end,
},
{
+2
View File
@@ -141,6 +141,7 @@
"Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"Qwen3.6-35B-A3B-Thinking": { "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"Qwen3.5-9B-UD-Q6_K_XL": { "name": "Qwen3.5 9B · 32k · vision — quick tasks (remote)", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
"Qwen3.8-27B-UD-IQ3_XXS": { "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist (remote)", "attachment": true, "limit": { "context": 24576, "output": 4096 } },
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (remote)", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
"GLM-4.7-Flash-UD-Q4_K_XL": { "name": "GLM-4.7 Flash · 24k — quality coder (remote)", "limit": { "context": 24576, "output": 8192 } },
@@ -160,6 +161,7 @@
"Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"Qwen3.6-35B-A3B-Thinking": { "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"Qwen3.5-9B-UD-Q6_K_XL": { "name": "Qwen3.5 9B · 32k · vision — quick tasks", "attachment": true, "limit": { "context": 32768, "output": 8192 } },
"Qwen3.8-27B-UD-IQ3_XXS": { "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner", "attachment": true, "limit": { "context": 24576, "output": 8192 } },
"gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist", "attachment": true, "limit": { "context": 24576, "output": 4096 } },
"gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs", "attachment": true, "limit": { "context": 65536, "output": 16384 } },
"GLM-4.7-Flash-UD-Q4_K_XL": { "name": "GLM-4.7 Flash · 24k — quality coder", "limit": { "context": 24576, "output": 8192 } },
+15 -8
View File
@@ -7,15 +7,22 @@
# cpu module icon (U+F035B).
printf '\U000f035b '
rt="${XDG_RUNTIME_DIR:-/tmp}"
st="$rt/tmux_cpu_stat" # previous totals: tot idle
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
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"
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))
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 -1
View File
@@ -1,5 +1,5 @@
#!/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
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
+12 -9
View File
@@ -10,8 +10,8 @@
# 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
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"
@@ -20,8 +20,8 @@ emit() { # emit <icon-printf-escape> <rate>
# 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
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
}
@@ -39,15 +39,18 @@ 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
drx=0
dtx=0
dt=1
if [ -r "$ctr" ]; then
read -r prx ptx pnow < "$ctr"
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=$((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"
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++ }
@@ -55,6 +58,6 @@ human() { awk -v b="$1" 'BEGIN{ split("B K M G T", u, " "); i=1;
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"
printf '%s %s %s\n' "$dl" "$ul" "$(date +%s)" >"$cache"
if [ "$which" = ul ]; then emit '\U0000f093' "$ul"; else emit '\U0000f019' "$dl"; fi
+14 -9
View File
@@ -5,15 +5,19 @@
# (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=""
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 ;;
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
@@ -21,17 +25,18 @@ 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)
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
case $t in '' | *[!0-9]*) continue ;; esac
if [ -z "$hw" ] || [ "$t" -gt "$hw" ]; then hw=$t; fi
done ;;
done
;;
esac
done
fi
t=${cpu_zone:-${hw:-$any_zone}}
[ -n "$t" ] || exit 0
printf '\U000f050f %d° ' $(( (t + 500) / 1000 ))
printf '\U000f050f %d° ' $(((t + 500) / 1000))
+71 -5
View File
@@ -3,6 +3,18 @@
set -g extended-keys on
set -g extended-keys-format csi-u
# 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,
# 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.
# 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).
#
# 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
# than kitty's scrollback, and a click focuses the pane under it.
set -g mouse on
# Number windows (and panes) from 1 instead of 0 — easier to reach on the
# keyboard. renumber-windows keeps them gap-free when a window is closed.
set -g base-index 1
@@ -42,8 +54,36 @@ set -g status-style "bg=default,fg=#cdd6f4" # transparent: no bar background
set -g status-left-length 40
set -g status-right-length 100
# Left: session name (mauve/bold) + thin divider.
set -g status-left " #[fg=#cba6f7,bold]#S #[fg=#45475a]│ "
# --- Mobile view -----------------------------------------------------------
# status-left and status-right are re-evaluated per client on every draw, and
# #{client_width} is that client's own width — so in `auto` mode a phone attached
# to the same session as the desktop renders the compact bars while the desktop
# keeps the full ones, simultaneously and with no intervention.
#
# @status_mode is auto (per-client, by width) | full | compact, cycled with
# prefix+S. Rather than branch each bar three ways, the mode picks the width a
# client has to beat — 0 nothing can lose, 9999 nothing can win — so each
# decision below stays a single comparison. The 100 is the switch point: the full
# right segment is ~60 columns before the window list even starts. Set
# @status_mode without -g to scope an override to one session, so a grouped
# mobile session can sit on `compact` while the desktop session stays on `auto`.
#
# Each variant lives in its own option instead of inline because #{?a,b,c} splits
# its branches on commas and the styles are full of them (#[fg=…,bold]); option
# values are substituted after the ternary is parsed, so those commas are never
# seen as separators.
#
# NOTE: the comparisons MUST use #{e|>=:…}. Plain #{>=:…} compares as strings, so
# "144" >= "100" is false and every client would get the compact bar.
set -g @status_mode auto
set -g @status_cutoff "#{?#{==:#{@status_mode},full},0,#{?#{==:#{@status_mode},compact},9999,100}}"
# Left: session name (mauve/bold) + thin divider. Compact clips the name to two
# characters — enough to tell sessions apart when every column counts. #{=2:…}
# takes a format NAME, so it's session_name here rather than the #S shorthand.
set -g @left_full " #[fg=#cba6f7,bold]#S #[fg=#45475a]│ "
set -g @left_compact " #[fg=#cba6f7,bold]#{=2:session_name} #[fg=#45475a]│ "
set -g status-left "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@left_full},#{E:@left_compact}}"
# Middle: flat window list — inactive muted, active mauve/bold, 2-space gaps.
set -g window-status-separator " "
@@ -52,9 +92,35 @@ setw -g window-status-current-format "#[fg=#cba6f7,bold]#I #[fg=#cdd6f4,bold]#W"
setw -g window-status-activity-style "fg=#f9e2af"
# Right: net dl/ul · cpu · mem │ date │ clock. continuum prepends its invisible
# save hook here (this block sits above the tpm `run` line, so the hook survives
# every reload). Metrics are scripts/*.sh, symlinked into ~/.config/tmux/scripts/.
set -g status-right "#[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 "
# save hook to status-right (this block sits above the tpm `run` line, so the
# hook survives every reload). Metrics are scripts/*.sh, symlinked into
# ~/.config/tmux/scripts/. The hook lands ahead of the #{?} below, i.e. 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 "
# 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.
set -g @right_compact "#[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) "
set -g status-right "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@right_full},#{E:@right_compact}}"
# Cycle auto -> compact -> full -> auto. Braces instead of nested quoted strings
# so the styles' commas and the inner if-shell don't need escaping.
bind S {
if -F '#{==:#{@status_mode},auto}' {
set -g @status_mode compact
display 'status bar: compact (forced)'
} {
if -F '#{==:#{@status_mode},compact}' {
set -g @status_mode full
display 'status bar: full (forced)'
} {
set -g @status_mode auto
display 'status bar: auto — switches at 100 columns'
}
}
refresh-client -S
}
# 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
+72 -10
View File
@@ -56,15 +56,70 @@ have a slash command to disable them for the current session without deleting.
| Extension | What it adds | Origin | Remove |
|---|---|---|---|
| `statusbar.ts` | Replaces the footer with a Claude-Code-style status line (cwd, git branch, model, context %, decode t/s, token counts) in Nerd Font glyphs; adds a streaming pulse + a ≥80%-context warning widget | **custom** | delete file → default footer returns; or `/statusbar` to toggle off for the session |
| `vim-editor.ts` | Modal (vim-like) input editor with `[I]/[N]/[V]` indicator, hjkl/word/line motions, counts, yank/paste via the kill-ring | **local fork** of pi's bundled `examples/extensions/modal-editor.ts` | delete file → default editor; or `/vim` to toggle |
| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it | **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) |
| `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) |
| `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 |
| `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 |
| `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 |
| `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 |
| `todo.ts` | A `todo` tool the model calls (add/toggle/list/clear) plus `/todos` to view it; state lives in tool results, so forking rewinds it correctly. Distinct from plan-mode's checklist | **verbatim** from pi's `examples/extensions/todo.ts` | delete file (it also adds a tool schema to every request) |
> The bundled examples live at `/opt/pi-coding-agent/examples/extensions/` — that's
> where `notify`/`questionnaire`/`modal-editor`/`plan-mode` came from, and where to
> re-pull the two forks after a pi upgrade before re-applying the local edits (the
> forks' own headers document those edits).
> where `notify`/`questionnaire`/`plan-mode` came from. `plan-mode/` is now the only
> **fork** left, so it's the only one to re-pull after a pi upgrade before re-applying
> the local edits (its header documents them). `notify`/`questionnaire` carry no edits
> worth keeping — just re-copy them. The modal editor used to be a fork of
> `modal-editor.ts` too; it was replaced by the `pi-vim` package on 2026-08-05 and the
> fork deleted on 2026-08-09 — `git show c6566af^:common/.pi/agent/extensions/vim-editor.ts`
> brings it back if ever wanted.
### Modal (vim) input — the `pi-vim` package
Modal editing is **not** an extension in this tree any more; it's the npm package
[`pi-vim`](https://github.com/lajarre/pi-vim), pinned by `settings.json > packages`
and installed into the gitignored `agent/npm/`. That swap is the point: `pi update
npm:pi-vim` instead of re-pulling and re-patching a fork on every pi upgrade.
Five modes (INSERT/NORMAL/VISUAL/V-LINE/EX) with the usual motions, operators, text
objects, undo and `.` repeat; the mode renders as a word label at the editor's
bottom-right, doubling as a pending-command display (` NORMAL 3d2w_ `). `:` dispatches
real pi commands and shells out with `:!`. No search (`/`, `?`, `n`), macros or
visual-block — upstream implements none of those.
```fish
pi install npm:pi-vim
```
> ⚠️ **This pi build needs pi-vim's peer dep installed by hand.** pi ships as a
> Bun-compiled ELF with the `@earendil-works/*` packages *embedded*, not on disk.
> pi-vim's `clipboard-mirror.ts` calls `import.meta.resolve("@earendil-works/pi-coding-agent")`
> at **module top level**, so after a bare `pi install` the resolve throws and the whole
> extension fails to load with `Cannot find module '@earendil-works/pi-coding-agent'`.
> No setting avoids it — the call runs at import time. Once per host, matching
> `pi --version` exactly:
>
> ```fish
> cd ~/.pi/agent/npm
> fnm exec --using=22 -- npm install --save-exact @earendil-works/pi-coding-agent@0.83.0
> ```
>
> `pi install` deliberately doesn't pull peers (it would duplicate the runtime). Costs
> ~170 MB, and wants re-pinning after every pi upgrade — hence `--save-exact`, since a
> caret range would quietly drift off `pi --version`.
Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI),
`borderSync`/`labelSync` pinned so only the label is tinted, and
`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every
`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
user-global settings file.
**Remove:** `pi remove npm:pi-vim`, and delete `agent/extensions/vim-toggle.ts` plus the
`piVim` block. `@burneikis/pi-vim` and `pi-vimmode` are alternatives that need no peer
install.
### Theme
@@ -110,11 +165,14 @@ Beyond pointing at local models, these values deviate from pi's defaults:
| Key | Value here | Stock default | Why |
|---|---|---|---|
| `compaction.reserveTokens` | `6144` | 16384 | small local windows (24k128k) — don't let the response reserve dominate a short window |
| `compaction.keepRecentTokens` | `6000` | 20000 | keep less verbatim so short windows don't thrash into repeated compaction |
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k128k), but **≥ every model's `maxTokens`** — otherwise a turn can overflow the window before compaction even fires |
| `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 |
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
| `defaultThinkingLevel` | `off` | — | most local models here are non-reasoning |
| `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 |
| `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
`compaction`/`retry` block).
@@ -140,8 +198,9 @@ model.
> config. `auth.json` is **not tracked** (gitignored as of 2026-08-08): pi's interactive
> `/login` rewrites it with the **literal** key, and a tracked copy would be one stray
> `/login` away from committing a real token. Bootstrap a new host with
> `cp auth.json.example auth.json`. For bash its `.bash_profile` since it's not tracked by
> git, stow not applied.
> `cp auth.json.example auth.json`. The key itself lives in a shell file that is *not*
> tracked and *not* stowed, so it never reaches this repo — `conf.d/secrets.fish` for
> fish, `~/.bash_profile` for bash.
---
@@ -150,7 +209,10 @@ model.
- **Just the status bar:** copy `agent/extensions/statusbar.ts` into your
`~/.pi/agent/extensions/`, re-stow/restart, `/reload`. Colors follow your active
theme automatically. Needs a Nerd Font terminal for the glyphs.
- **Just vim input:** copy `agent/extensions/vim-editor.ts`, `/reload`, `/vim`.
- **Just vim input:** `pi install npm:pi-vim`, then the peer-dep install above (this
build needs it), then `/reload`. Copy the `piVim` block from `settings.json` for the
same colors and clipboard behaviour, and `agent/extensions/vim-toggle.ts` if you want
`/vim` to toggle it.
- **Just plan mode:** copy `agent/extensions/plan-mode/` **and** add the
`app.thinking.cycle` remap to your `keybindings.json` (else Shift+Tab collides).
- **Just the theme:** copy `agent/themes/catppuccin-mocha.json`, set
+22
View File
@@ -0,0 +1,22 @@
# Global agent instructions
You are a coding assistant running on small, locally-hosted models. Be precise and economical with tokens.
## Working style
- Act with tools instead of describing what you would do. Keep prose short.
- Do the task that was asked. Don't add unrequested changes, refactors, or files.
- When done, stop. A one- or two-line summary is enough; no recaps of obvious steps.
## Files & edits
- Read a file before you edit it. Never guess at file paths, function names, or APIs — verify first.
- Use the edit tool for changes. Make minimal, targeted diffs; never paste an entire file back to the user.
- Match the surrounding code's style, naming, and imports.
## Shell
- Run one command at a time and check its output before the next.
- Prefer `rg` and `fd` for search. Use read-only commands when exploring.
- Never run destructive or system-changing commands (`rm -rf`, `sudo`, package installs, force-push) unless explicitly asked.
## Honesty
- If you're unsure, say so and check rather than inventing an answer.
- Report failures plainly with the actual error; don't claim success you didn't verify.
@@ -0,0 +1,60 @@
/**
* Compaction Watch
*
* 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
* visible, and specifically names the failure mode that was breaking it here before
* 2026-08-16.
*
* pi reports a `reason` on every compaction:
* "manual" - you ran /compact
* "threshold" - the healthy path: contextTokens passed contextWindow - reserveTokens
* "overflow" - the window was ALREADY full; this is recovery, not prevention
*
* An "overflow" compaction means the turn blew past contextWindow before the threshold
* could fire, which happens whenever `compaction.reserveTokens` is smaller than the
* model's `maxTokens`. pi then gets one recovery attempt, summarizing from an
* 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
* model in use, and this says so at the moment it happens instead of leaving you to
* infer it later. See CLAUDE.md > Compaction tuning.
*
* 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
* warning available.
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
let overflowCount = 0;
pi.on("session_before_compact", async (event, ctx) => {
if (!ctx.hasUI) return undefined;
if (event.reason === "overflow") {
overflowCount++;
ctx.ui.notify(
`Compacting in OVERFLOW recovery (#${overflowCount}) — the context window was already ` +
`full before compaction could fire. Raise compaction.reserveTokens above this model's ` +
`maxTokens, or cap maxTokens in models.json. See CLAUDE.md > Compaction tuning.`,
"warning",
);
}
// Never alter the compaction itself — observe only.
return undefined;
});
pi.on("session_compact", async (event, ctx) => {
if (!ctx.hasUI) return;
const reason = event.reason ?? "unknown";
const retrying = event.willRetry ? ", retrying the aborted turn" : "";
const level = reason === "overflow" ? "warning" : "info";
ctx.ui.notify(`Compacted (${reason}${retrying})`, level);
});
}
@@ -0,0 +1,59 @@
/**
* Confirm Destructive Actions Extension
*
* Prompts for confirmation before destructive session actions (clear, switch, branch).
* Demonstrates how to cancel session events using the before_* events.
*/
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
if (!ctx.hasUI) return;
if (event.reason === "new") {
const confirmed = await ctx.ui.confirm(
"Clear session?",
"This will delete all messages in the current session.",
);
if (!confirmed) {
ctx.ui.notify("Clear cancelled", "info");
return { cancel: true };
}
return;
}
// reason === "resume" - check if there are unsaved changes (messages since last assistant response)
const entries = ctx.sessionManager.getEntries();
const hasUnsavedWork = entries.some(
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
);
if (hasUnsavedWork) {
const confirmed = await ctx.ui.confirm(
"Switch session?",
"You have messages in the current session. Switch anyway?",
);
if (!confirmed) {
ctx.ui.notify("Switch cancelled", "info");
return { cancel: true };
}
}
});
pi.on("session_before_fork", async (event, ctx) => {
if (!ctx.hasUI) return;
const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [
"Yes, create fork",
"No, stay in current session",
]);
if (choice !== "Yes, create fork") {
ctx.ui.notify("Fork cancelled", "info");
return { cancel: true };
}
});
}
@@ -0,0 +1,69 @@
/**
* Permission Gate Extension
*
* Prompts for confirmation before running potentially dangerous bash commands.
*
* Adapted from pi's bundled `examples/extensions/permission-gate.ts`. Upstream checks
* three patterns (rm -rf, sudo, chmod/chown 777); the list below is extended to cover
* the rules AGENTS.md only *asks* for. That distinction is the whole point of running
* this: AGENTS.md is a prompt, and the small local models this config targets follow
* prompts unreliably. This is the enforcement half the same trick plan-mode already
* uses when it disables edit/write outright.
*
* In the TUI a match prompts (so "Yes" still gets you through); with no UI it blocks.
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const dangerousPatterns = [
// --- upstream ---
/\brm\s+(-rf?|--recursive)/i,
/\bsudo\b/i,
/\b(chmod|chown)\b.*777/i,
// --- LOCAL: git history is the user's to write, never the agent's ---
// The standing rule is that commits and pushes are made by hand, after review.
/\bgit\s+(commit|push|revert)\b/i,
/\bgit\s+reset\s+--hard\b/i,
/\bgit\s+(rebase|cherry-pick)\b/i,
/\bgit\s+clean\s+-[a-z]*[fd]/i,
/\bgit\s+stash\s+(drop|clear|pop)\b/i,
/--force\b|--force-with-lease\b/i,
// --- LOCAL: system-changing commands AGENTS.md rules out ---
/\b(pacman|yay|paru)\s+-[A-Za-z]*[SRU]/,
/\b(npm|pnpm|yarn)\s+(install|i|add|remove|uninstall)\b/i,
/\bpip3?\s+(install|uninstall)\b/i,
/\bcargo\s+(install|uninstall)\b/i,
/\bsystemctl\s+(start|stop|restart|enable|disable|mask)\b/i,
// --- LOCAL: irreversible disk / process operations ---
/\bmkfs\b|\bdd\s+if=|\bshred\b|\bwipefs\b/i,
/\b(reboot|shutdown|poweroff|halt)\b/i,
/\b(killall|pkill)\b/i,
/>\s*\/dev\/(sd|nvme|vd)/i,
];
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const command = event.input.command as string;
const matched = dangerousPatterns.find((p) => p.test(command));
if (matched) {
if (!ctx.hasUI) {
// Non-interactive (`pi -p`): nothing can confirm, so refuse.
return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" };
}
const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
if (choice !== "Yes") {
return { block: true, reason: "Blocked by user" };
}
}
return undefined;
});
}
@@ -142,6 +142,25 @@ export default function planModeExtension(pi: ExtensionAPI): void {
handler: async (_args, ctx) => togglePlanMode(ctx),
});
// LOCAL EDIT: the checklist widget only tears itself down once every step is
// marked complete, which needs the model to emit a [DONE:n] tag per step. When
// one is missed the widget sticks around after the work is finished, so give
// the user a way out that doesn't involve toggling plan mode on and off again.
pi.registerCommand("plan-done", {
description: "Clear a stuck plan checklist (stops execution tracking)",
handler: async (_args, ctx) => {
if (!executionMode && todoItems.length === 0) {
ctx.ui.notify("No plan is being tracked.");
return;
}
executionMode = false;
todoItems = [];
updateStatus(ctx);
persistState();
ctx.ui.notify("Plan tracking cleared.");
},
});
pi.registerCommand("todos", {
description: "Show current plan todo list",
handler: async (_args, ctx) => {
+10 -3
View File
@@ -151,9 +151,16 @@ export function extractTodoItems(message: string): TodoItem[] {
export function extractDoneSteps(message: string): number[] {
const steps: number[] = [];
for (const match of message.matchAll(/\[DONE:(\d+)\]/gi)) {
const step = Number(match[1]);
if (Number.isFinite(step)) steps.push(step);
// LOCAL EDIT: upstream matched only the literal `[DONE:3]`. Small local models
// drift on it, and one missed tag pins the plan widget on screen forever, so
// also accept `[DONE 3]`, `[DONE-3]` and comma lists `[DONE:1,2]`. The brackets
// stay required on purpose: a bare "done 3" in prose must not mark a step.
for (const match of message.matchAll(/\[\s*DONE\s*[:\-=]?\s*([\d,\s]+?)\s*\]/gi)) {
for (const part of match[1].split(/[,\s]+/)) {
if (part === "") continue;
const step = Number(part);
if (Number.isFinite(step)) steps.push(step);
}
}
return steps;
}
@@ -0,0 +1,58 @@
/**
* Protected Paths Extension
*
* Blocks write and edit operations to protected paths.
*
* Adapted from pi's bundled `examples/extensions/protected-paths.ts`. Upstream ships a
* three-entry demo list (.env, .git/, node_modules/); the list below names the files
* that actually matter in this dotfiles tree the ones holding secrets, and the git
* metadata the agent has no business rewriting.
*
* Scope, so this isn't over-trusted: it guards against the *model* calling write/edit.
* It cannot stop pi's own `/login`, which rewrites auth.json with the literal key
* that is why auth.json is gitignored and auth.json.example is the tracked copy.
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
const protectedPaths = [
// Secrets. `secrets.fish` holds $DUSKADIY_API_KEY; auth.json is pi's own
// credential map and must keep $VAR references rather than literals.
"secrets.fish",
"auth.json",
".env",
"fish_variables",
// Credentials outside the repo.
".ssh/",
".gnupg/",
".aws/",
".netrc",
// Repo plumbing and vendored trees — never hand-edited.
".git/",
"node_modules/",
];
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "write" && event.toolName !== "edit") {
return undefined;
}
const path = event.input.path as string;
// Substring match, like upstream: cheap, and these names are distinctive enough.
// Note `auth.json.example` is intentionally caught too — it is a tracked file
// whose whole job is to contain no secret, so an agent rewrite is worth a look.
const isProtected = protectedPaths.some((p) => path.includes(p));
if (isProtected) {
if (ctx.hasUI) {
ctx.ui.notify(`Blocked write to protected path: ${path}`, "warning");
}
return { block: true, reason: `Path "${path}" is protected` };
}
return undefined;
});
}
@@ -0,0 +1,27 @@
/**
* Session naming example.
*
* Shows setSessionName/getSessionName to give sessions friendly names
* that appear in the session selector instead of the first message.
*
* Usage: /session-name [name] - set or show session name
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerCommand("session-name", {
description: "Set or show session name (usage: /session-name [new name])",
handler: async (args, ctx) => {
const name = args.trim();
if (name) {
pi.setSessionName(name);
ctx.ui.notify(`Session named: ${name}`, "info");
} else {
const current = pi.getSessionName();
ctx.ui.notify(current ? `Session: ${current}` : "No session name set", "info");
}
},
});
}
+297
View File
@@ -0,0 +1,297 @@
/**
* Todo Extension - Demonstrates state management via session entries
*
* This extension:
* - Registers a `todo` tool for the LLM to manage todos
* - Registers a `/todos` command for users to view the list
*
* State is stored in tool result details (not external files), which allows
* proper branching - when you branch, the todo state is automatically
* correct for that point in history.
*/
import { StringEnum } from "@earendil-works/pi-ai";
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
import { matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
import { Type } from "typebox";
interface Todo {
id: number;
text: string;
done: boolean;
}
interface TodoDetails {
action: "list" | "add" | "toggle" | "clear";
todos: Todo[];
nextId: number;
error?: string;
}
const TodoParams = Type.Object({
action: StringEnum(["list", "add", "toggle", "clear"] as const),
text: Type.Optional(Type.String({ description: "Todo text (for add)" })),
id: Type.Optional(Type.Number({ description: "Todo ID (for toggle)" })),
});
/**
* UI component for the /todos command
*/
class TodoListComponent {
private todos: Todo[];
private theme: Theme;
private onClose: () => void;
private cachedWidth?: number;
private cachedLines?: string[];
constructor(todos: Todo[], theme: Theme, onClose: () => void) {
this.todos = todos;
this.theme = theme;
this.onClose = onClose;
}
handleInput(data: string): void {
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
this.onClose();
}
}
render(width: number): string[] {
if (this.cachedLines && this.cachedWidth === width) {
return this.cachedLines;
}
const lines: string[] = [];
const th = this.theme;
lines.push("");
const title = th.fg("accent", " Todos ");
const headerLine =
th.fg("borderMuted", "─".repeat(3)) + title + th.fg("borderMuted", "─".repeat(Math.max(0, width - 10)));
lines.push(truncateToWidth(headerLine, width));
lines.push("");
if (this.todos.length === 0) {
lines.push(truncateToWidth(` ${th.fg("dim", "No todos yet. Ask the agent to add some!")}`, width));
} else {
const done = this.todos.filter((t) => t.done).length;
const total = this.todos.length;
lines.push(truncateToWidth(` ${th.fg("muted", `${done}/${total} completed`)}`, width));
lines.push("");
for (const todo of this.todos) {
const check = todo.done ? th.fg("success", "✓") : th.fg("dim", "○");
const id = th.fg("accent", `#${todo.id}`);
const text = todo.done ? th.fg("dim", todo.text) : th.fg("text", todo.text);
lines.push(truncateToWidth(` ${check} ${id} ${text}`, width));
}
}
lines.push("");
lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width));
lines.push("");
this.cachedWidth = width;
this.cachedLines = lines;
return lines;
}
invalidate(): void {
this.cachedWidth = undefined;
this.cachedLines = undefined;
}
}
export default function (pi: ExtensionAPI) {
// In-memory state (reconstructed from session on load)
let todos: Todo[] = [];
let nextId = 1;
/**
* Reconstruct state from session entries.
* Scans tool results for this tool and applies them in order.
*/
const reconstructState = (ctx: ExtensionContext) => {
todos = [];
nextId = 1;
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role !== "toolResult" || msg.toolName !== "todo") continue;
const details = msg.details as TodoDetails | undefined;
if (details) {
todos = details.todos;
nextId = details.nextId;
}
}
};
// Reconstruct state on session events
pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
// Register the todo tool for the LLM
pi.registerTool({
name: "todo",
label: "Todo",
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
parameters: TodoParams,
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
switch (params.action) {
case "list":
return {
content: [
{
type: "text",
text: todos.length
? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n")
: "No todos",
},
],
details: { action: "list", todos: [...todos], nextId } as TodoDetails,
};
case "add": {
if (!params.text) {
return {
content: [{ type: "text", text: "Error: text required for add" }],
details: { action: "add", todos: [...todos], nextId, error: "text required" } as TodoDetails,
};
}
const newTodo: Todo = { id: nextId++, text: params.text, done: false };
todos.push(newTodo);
return {
content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }],
details: { action: "add", todos: [...todos], nextId } as TodoDetails,
};
}
case "toggle": {
if (params.id === undefined) {
return {
content: [{ type: "text", text: "Error: id required for toggle" }],
details: { action: "toggle", todos: [...todos], nextId, error: "id required" } as TodoDetails,
};
}
const todo = todos.find((t) => t.id === params.id);
if (!todo) {
return {
content: [{ type: "text", text: `Todo #${params.id} not found` }],
details: {
action: "toggle",
todos: [...todos],
nextId,
error: `#${params.id} not found`,
} as TodoDetails,
};
}
todo.done = !todo.done;
return {
content: [{ type: "text", text: `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}` }],
details: { action: "toggle", todos: [...todos], nextId } as TodoDetails,
};
}
case "clear": {
const count = todos.length;
todos = [];
nextId = 1;
return {
content: [{ type: "text", text: `Cleared ${count} todos` }],
details: { action: "clear", todos: [], nextId: 1 } as TodoDetails,
};
}
default:
return {
content: [{ type: "text", text: `Unknown action: ${params.action}` }],
details: {
action: "list",
todos: [...todos],
nextId,
error: `unknown action: ${params.action}`,
} as TodoDetails,
};
}
},
renderCall(args, theme, _context) {
let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
return new Text(text, 0, 0);
},
renderResult(result, { expanded }, theme, _context) {
const details = result.details as TodoDetails | undefined;
if (!details) {
const text = result.content[0];
return new Text(text?.type === "text" ? text.text : "", 0, 0);
}
if (details.error) {
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
}
const todoList = details.todos;
switch (details.action) {
case "list": {
if (todoList.length === 0) {
return new Text(theme.fg("dim", "No todos"), 0, 0);
}
let listText = theme.fg("muted", `${todoList.length} todo(s):`);
const display = expanded ? todoList : todoList.slice(0, 5);
for (const t of display) {
const check = t.done ? theme.fg("success", "✓") : theme.fg("dim", "○");
const itemText = t.done ? theme.fg("dim", t.text) : theme.fg("muted", t.text);
listText += `\n${check} ${theme.fg("accent", `#${t.id}`)} ${itemText}`;
}
if (!expanded && todoList.length > 5) {
listText += `\n${theme.fg("dim", `... ${todoList.length - 5} more`)}`;
}
return new Text(listText, 0, 0);
}
case "add": {
const added = todoList[todoList.length - 1];
return new Text(
theme.fg("success", "✓ Added ") +
theme.fg("accent", `#${added.id}`) +
" " +
theme.fg("muted", added.text),
0,
0,
);
}
case "toggle": {
const text = result.content[0];
const msg = text?.type === "text" ? text.text : "";
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
}
case "clear":
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0);
}
},
});
// Register the /todos command for users
pi.registerCommand("todos", {
description: "Show all todos on the current branch",
handler: async (_args, ctx) => {
if (ctx.mode !== "tui") {
ctx.ui.notify("/todos requires interactive mode", "error");
return;
}
await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
return new TodoListComponent(todos, theme, () => done());
});
},
});
}
+26 -6
View File
@@ -52,7 +52,7 @@
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
@@ -62,7 +62,7 @@
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
@@ -75,6 +75,16 @@
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.8-27B-UD-IQ3_XXS",
"name": "Qwen3.8 27B · 24k · vision — hybrid reasoner",
"reasoning": true,
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q4_K_XL",
"name": "GLM-4.7 Flash · 24k — quality coder",
@@ -82,7 +92,7 @@
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
@@ -157,7 +167,7 @@
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
@@ -167,7 +177,7 @@
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
@@ -180,6 +190,16 @@
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.8-27B-UD-IQ3_XXS",
"name": "Qwen3.8 27B · 24k · vision — hybrid reasoner",
"reasoning": true,
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text", "image"],
"contextWindow": 24576,
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q4_K_XL",
"name": "GLM-4.7 Flash · 24k — quality coder",
@@ -187,7 +207,7 @@
"compat": { "thinkingFormat": "qwen-chat-template" },
"input": ["text"],
"contextWindow": 24576,
"maxTokens": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
+3 -2
View File
@@ -4,6 +4,7 @@
"defaultThinkingLevel": "off",
"theme": "catppuccin-mocha",
"quietStartup": false,
"collapseChangelog": true,
"defaultProjectTrust": "ask",
"enableInstallTelemetry": false,
"enabledModels": [
@@ -12,8 +13,8 @@
],
"compaction": {
"enabled": true,
"reserveTokens": 6144,
"keepRecentTokens": 6000
"reserveTokens": 8192,
"keepRecentTokens": 4000
},
"retry": {
"enabled": true,
+14 -3
View File
@@ -4,7 +4,8 @@ llama.cpp in **router mode** serving ~10 model presets from one 16 GB GPU.
Last full tune: **2026-07-16** (decode numbers measured then via `bench.py`, unless a preset comment in `config.ini` says otherwise).
Last full clean sweep: **2026-08-08 03:22** — all 11 presets loaded and ran green (baseline 2.0 GB) after the week's coder-race/KV-fix/gemma-sidecar round.
- **Box**: AMD RX 7600 XT 16 GB (ROCm, ~288 GB/s) + Ryzen 5600X (6 cores) + 48 GB RAM.
- **Box**: AMD RX 7600 XT 16 GB (~288 GB/s; backend = Arch `llama.cpp-vulkan` / RADV, **not** ROCm —
`rocm-smi` is only the VRAM monitor) + Ryzen 5600X (6 cores) + 48 GB RAM.
⚠ The GPU **also drives the display** — see [VRAM safety](#vram-safety-the-golden-rules).
- **Endpoints**: LAN `http://$SERVER:11343/v1` · remote `https://<own-domain>/api/v1`
(own reverse proxy to the same server; requires an API key. Only the LAN endpoint is keyless).
@@ -36,7 +37,7 @@ After ANY preset change: restart + `./bench.py -m <changed-ids>` and check the g
## VRAM safety (the golden rules)
ROCm does not OOM cleanly — an overcommitted model spills into GTT (system RAM),
The amdgpu driver does not OOM cleanly — an overcommitted model spills into GTT (system RAM),
starves the desktop and freezes the PC (reboot). Hard-won rules:
1. Keep every preset's benched **VRAM free ≥ 2.5 GB** (idle desktop uses ~1.3 of 17.2).
@@ -60,6 +61,7 @@ starves the desktop and freezes the PC (reboot). Hard-won rules:
| `Qwen3-Coder-30B-Instruct-UD-Q3_K_XL` | **~30** clean / 27.0 evening @moe12 | 32k | — | **main agent coder** — won the 2026-08-06 quant race; moe12 verified evening 08-07 + 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 |
| `Qwen3-Coder-Next-UD-IQ3_XXS` | 16.0 | **128k** | 80B-A3B | long-session coder (128k ctx) |
| `Qwen3.8-27B-UD-IQ3_XXS` | 14.5 (10.9 w/o MTP) | 24k | vision, MTP, hybrid reasoner | ⚠ **clean-desktop specialty** (kept 09-10 over the ≥22 gate) — dense-27B quality, slow: its Gated-DeltaNet layers are Vulkan-bound (prefill ~30). 2.5 GB free @1.6 baseline @24k (✓ 09-10); **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) |
Expected run-to-run spread: MTP models swing ±15% with draft **acceptance rate** (content-
@@ -77,6 +79,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).
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).
- **Best single-model quality, speed no object** (quiet desktop only): `Qwen3.8-27B` — dense
27B hybrid reasoner + vision at ~14.5 t/s; it cannot go faster on this card (GDN layers on
Vulkan), and 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,
`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
@@ -123,6 +128,9 @@ The embedder is deliberately absent from chat clients.
2026-08-06: the coder id changed `…-IQ4_XS``…-UD-Q3_K_XL` in all six client files
(common + lw overlays) after the quant race; OWUI picks the new id up automatically from
the router, but chats/presets saved against the old id need re-picking.
2026-08-16: `Qwen3.8-27B-UD-IQ3_XXS` (⚗ candidate) added to all six client files.
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.
## Tuning cheat-sheet
@@ -135,7 +143,10 @@ the router, but chats/presets saved against the old id need re-picking.
4-bit now; 3-bit was only ~2 t/s faster). Coders want ≥4-bit; chat tolerates UD 3-bit.
- **MTP / spec decode** (`spec-type draft-mtp`): ~1.52× decode. Qwen3.5/3.6 embed the
head in the *MTP-repo* GGUF (same filename as plain repo — size is the tell);
gemma-4 uses a separate `mtp-*.gguf` draft file. **No MTP possible yet for**:
gemma-4 uses a separate `mtp-*.gguf` draft file; Qwen3.8 ships the head in the
*main*-repo GGUFs for ≥ Q2_K_XL (≤ IQ2_S are stripped — use the repo's `MTP/mtp-*.gguf`
draft instead). On the hybrid 27B it is only +33% (10.9→14.5) and costs 1.5 GB: every
speculative step re-runs the Gated-DeltaNet layers. **No MTP possible yet for**:
GLM-4.7-Flash (conversion drops the head — expected ~1.5× when llama.cpp lands it),
Qwen3-Coder-Next (Qwen3-Next head exists upstream, no GGUF ships it), gpt-oss and
Qwen3-Coder-30B (no head exists). Recheck releases occasionally.
+138 -2
View File
@@ -1,7 +1,8 @@
# llama.cpp model config — AMD RX 7600 XT (ROCm) · 16 GB (17.16 GB total)
# llama.cpp model config — AMD RX 7600 XT · 16 GB (17.16 GB total) · backend = Arch llama.cpp-vulkan
# (RADV, device Vulkan0 — NOT ROCm/HIP; rocm-smi is used for monitoring only)
# ═════════════════════════════════════════════════════════════════════════════
# ⚠⚠ VRAM SAFETY — READ THIS. This GPU ALSO DRIVES THE DISPLAY. If a model asks for
# more VRAM than is free, ROCm does NOT OOM cleanly — it spills into system RAM
# more VRAM than is free, the amdgpu driver does NOT OOM cleanly — it spills into system RAM
# (GTT), which starves the desktop and FREEZES THE WHOLE PC (reboot required).
# → TARGET ≥ ~2.5 GB free (≤ ~14.5 GB used). HARD FLOOR 1.5 GB (bench.py guard stops there).
# Idle desktop uses ~1.3 GB of 17.16 — but a browser/leftover model can hold 1-3 GB more,
@@ -223,6 +224,141 @@ repeat-penalty = 1.05
jinja = on
sleep-idle-seconds = 60
[Qwen3.8-27B-UD-IQ3_XXS]
# ⚠ CLEAN-DESKTOP SPECIALTY — KEPT by user decision 2026-09-10 despite failing the ≥22 t/s speed
# gate (14.5 t/s MTP / 10.9 base; 2.8 GB free @1.5 baseline @16k). Pick it ONLY on a quiet
# desktop: at a 3+ GB evening baseline it lands ~1 GB free = GTT-spill/freeze territory. Role:
# slow-but-smart dense-27B answers + vision. Origin (added 2026-08-16): dense 27B, but NOT like the two
# retired dense 27Bs: hybrid attention (only 16 of 64 layers full-attn, rest linear/SSM →
# cheap KV, Coder-Next-style) AND an embedded MTP head — VERIFIED in the gguf header:
# qwen35.nextn_predict_layers = 1, block_count = 65 (64+1), arch "qwen35" = the same
# draft-mtp path the working 9B preset uses. Hybrid reasoner (thinking ON by default,
# reasoning_effort tunable via chat-template-kwargs if a -low alias is ever wanted),
# vision, n_ctx_train = 262144.
# Speed math: dense 11.9 GB weights on ~288 GB/s ≈ 14-16 t/s base; MTP should lift to
# ~22-30 = roster-competitive IF the draft engages. If it doesn't, this lands in
# retired-dense-27B territory (~10-14) → retire again, quality won't pay for the speed.
# ⚠ BENCH HISTORY — still no green row, decode/MTP UNMEASURED after 5 attempts:
# · 08-16 14:59/15:19/15:29 (3.3-3.5 DIRTY baseline): 16.5-16.7 used / 0.4-0.7 free, guard
# skipped gen every time. Implied footprint 13.2 GB — MISLEADING, see below.
# · 08-17 10:31: ERROR conn-refused = bench raced llama.service coming up after boot.
# · 08-17 10:38 (1.9 near-clean baseline): 15.9 used / 1.3 free — guard floor missed by
# 0.2. TRUE footprint = 14.0 GB over baseline; the dirty runs looked 0.8 smaller because
# ROCm silently spilled that much to GTT (the exact freeze precursor the guards watch).
# · 08-17 10:43 (post-trim, 10.0 leftover start, partial drain): 15.8 / 1.4 — floor missed
# by 0.1. Trim verified live via router args; ~2.4 effective baseline poisoned it again.
# · 08-17 10:53 (1.9 baseline, NO leftovers — a genuine desktop-idle run): 15.8 / 1.4,
# floor missed by 0.1 again. DECISIVE: trimmed footprint 13.9 vs untrimmed 14.0 — both
# knobs together saved 0.1 GB. ctx/ubatch do NOT move this model (fixed SSM-state +
# big-vocab buffers). Absolute best case ≈ 2.0 free at a pristine 1.3 baseline @16k —
# permanently below the 2.5 target. → RETIREMENT RECOMMENDED 2026-08-17; decode/MTP
# never measured, academic at these margins. Third dense ~27B to die on this card,
# first one killed by fixed buffers rather than bandwidth.
# Consequences: full-ctx even on a 1.3 CLEAN night = ~15.3 used / 1.9 free (runs, but
# under the 2.5 target). EVENING regime can never fit this quant — that needs ≤ UD-IQ2_M,
# a quality gutting. → 08-17: TRIMMED FOR THE GATE BENCH: ctx 24576→16384 (~-0.25) +
# ubatch 512→256 (~-0.3-0.5) → predicted ~15.2 used / ~2.0 free @1.9 baseline. Clients
# still say 24576 — deliberately NOT re-wired for a temporary trim; do not use from
# clients anyway until this passes (a busy-desktop pick is one tab from the GTT freeze).
# DECISION GATE (superseded by the 09-10 user decision below): MTP ≥ ~22 t/s keeps it (clean-desktop specialty, restore ctx
# 24576 + re-check), anything less → retire + delete (dense 27Bs die on this card).
# ↻ REOPENED 2026-09-09 — the file on disk is STALE: unsloth re-quantized the whole repo as
# "Dynamic v3" on 2026-08-19, three days AFTER the 08-16 download (~10% better accuracy at
# equal size, per unsloth). Verified via the HF tree API at the pre/post commits:
# UD-IQ3_XXS 11.91 → 10.93 GB (SAME filename — re-download overwrites in place, no client
# or section changes); UD-Q2_K_XL 10.68 → 9.83; UD-IQ2_M DELETED (gone as an option);
# UD-IQ2_S 8.37 / UD-IQ2_XXS 7.27 are new 2-bit tiers. Header range-read 09-09: v3
# IQ3_XXS and Q2_K_XL both still carry nextn_predict_layers = 1 (MTP head embedded);
# unsloth stripped the head from ≤ IQ2_S only and ships MTP/mtp-Qwen3.8-27B-Q4_0.gguf
# as a separate model-draft for those (gemma-4 style).
# Predicted with v3 IQ3_XXS (footprint 14.0 0.98 ≈ 13.0; bench.py GB = decimal):
# @1.9 real-idle baseline ≈ 14.815.0 used / 2.22.4 free · @1.3 pristine ≈ 14.2 / ~2.9.
# Clears the 1.5 floor by ~0.8 even at 1.9 → the gen bench can FINALLY run; still ~0.10.3
# under the 2.5 target on a real idle desktop, clears it outright on a pristine one. Overhead datum: a third-party paired bench measured
# draft-mtp at ~+0.75 GB on this model (unsloth docs: "1-2 GB extra headroom") — a good
# part of the "fixed" ~2.1 GB is MTP + KV, not only SSM-state/vocab buffers.
# NEXT STEP (recommended, NOT yet done): re-download v3 IQ3_XXS on fl (verify 10.93 GB /
# 10.18 GiB before restarting), dotsync, restart llama.service, clean-baseline bench.
# Then the gate above decides — with one refinement: free 2.02.5 at ≥22 t/s = user's call
# between "clean-desktop specialty" and stepping down to v3 UD-Q2_K_XL (predicted ~13.7 /
# 3.5 @1.9 — needs the section-rename round across the six client files; sequence it
# after any in-flight client edits). Decode < 22 → retire regardless of VRAM.
# No in-family alternative: the Qwen3.8 lineup is 27B dense, Flash-Next 125B-A6B (smallest
# GGUF 72.5 GB, 75 GB RAM floor even with mmap'd n-gram tables — out on 48+16),
# 2.4T-A95B and Max. No 3.8 MoE in the 35B-A3B class exists; watch HF.
# ✦ GATE BENCH RAN 2026-09-10 02:36 (v3 file, sha verified; 2.1 baseline): 14.9 used / 2.2 free
# (footprint 12.8 — VRAM half of the gate PASSED, borderline) but decode 14.8 t/s, prefill 19,
# TTFT 11.7 s → speed half FAILED. Server log: MTP head loaded, draft acceptance 0.80
# (157/195, mean len 2.6) — so MTP IS engaging; at 2.6 tok/step that is ~175 ms per step ≈
# 34× a plain forward pass. NOTE the box runs the Arch llama.cpp-vulkan package (RADV,
# device Vulkan0) — NOT HIP/ROCm (this header said "ROCm" until 09-10); rocm-smi = monitoring only.
# Suspects: the 48 GatedDeltaNet layers on the Vulkan backend (upstream #20354: missing/slow
# GDN shader → CPU fallback) and/or recurrent-state checkpoint/restore per speculative step
# (~150 MiB for this model; reported net-negative on hybrid 27Bs). The hybrid 35B-A3B does
# 37 t/s on the same backend, so it is size-specific, not Vulkan-generic.
# A/B NEXT: [Qwen3.8-27B-UD-IQ3_XXS-nomtp] (TEMP alias, used 03:19 then removed) measures
# base decode; base ≥ 18 → MTP is the net negative here, run without it (frees ~0.75 GB);
# base ≈ 9 → the GDN path is the wall → retire. Also read "graph splits" from the load log.
# cache_reuse is inert here ("not supported by multimodal") — true for every mmproj preset.
# Re-run 03:07 @0.9 PRISTINE baseline: 15.0 t/s / 17 pp / 13.9 used / 3.3 free (footprint 13.0)
# — reproducible; the VRAM half is now fully closed, only the speed half is open.
# ✦ A/B 2026-09-10 03:19 (@1.5 baseline, same run): MTP 14.5 t/s / 30 pp / 14.3 used / 2.8 free
# vs NO-MTP 10.9 t/s / 34 pp / 12.8 used / 4.4 free. → MTP WORKS (+33%, matches the sudoingX
# paired benches) and costs 1.5 GB VRAM; the wall is the BASE: 10.9 t/s = 119 GB/s effective
# (41% of the card's 288) — the 48 GatedDeltaNet layers on the Vulkan/RADV backend, exactly
# the #20354 datum (Qwen3.5-27B ≈ 11.8 t/s on Strix Halo, either backend). Prefill 30-34 on
# a fully-GPU model is the same wall (the hybrid 35B-A3B daily driver's 64 pp is GDN-bound
# too). "graphs reused" 97-260 in the log → not a graph-rebuild problem. Nothing in this
# preset can move it; a bigger K-quant (faster dequant on Vulkan) does not fit. Perfect-
# kernel ceiling on this card ≈ 20 base / ~27 MTP — an upstream Vulkan gated_delta_net
# shader improvement COULD flip it; nothing else can.
# VERDICT 2026-09-10: gate FAILED on speed (14.5 < 22) with the VRAM half passed (2.8 free
# @1.5). Per the gate → retire. Re-probe only after a llama.cpp-vulkan bump whose changelog
# touches gated_delta_net / Vulkan GDN: `./bench.py -m Qwen3.8-27B-UD-IQ3_XXS`.
# → USER DECISION 2026-09-10: KEEP as clean-desktop specialty (overrides the gate). ctx restored
# to 24576 (clients already say 24576), ubatch stays 256 (prefill is GDN-bound, the bigger
# buffer buys nothing), MTP stays (+33% for 1.5 GB). ✓ CONFIRMED @24576 2026-09-10 03:51
# (1.6 baseline): 15.1 t/s / 31 pp / 14.6 used / 2.5 free — footprint 13.0, exactly as
# predicted; free = the 2.5 target at a normal idle, ~2.8 pristine, ~0.8 on a 3.3 evening.
# ⚠ 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 /
# missing-tensor errors, update llama.cpp on fl before touching this preset.
# ⬇ DOWNLOAD from unsloth/Qwen3.8-27B-GGUF (NO separate MTP repo — unlike Qwen3.5/3.6, the
# head ships in the MAIN repo's ggufs for ≥ Q2_K_XL; ≤ IQ2_S need MTP/mtp-Qwen3.8-27B-Q4_0.gguf):
# Qwen3.8-27B-UD-IQ3_XXS.gguf (10.93 GB Dynamic v3 since 2026-08-19 — the 08-16 download
# is the 11.91 GB v2; RE-FETCH. UD-IQ4_XS 14.3 GB and
# UD-Q3_K_XL 13.15 = dead on arrival, no n-cpu-moe escape)
# mmproj-Qwen3.8-27B-F16.gguf ← mmproj-F16.gguf (928 MB — unchanged; rename; watch trailing spaces)
model = /home/anon/software/models/Qwen3.8-27B-UD-IQ3_XXS.gguf
mmproj = /home/anon/software/models/mmproj-Qwen3.8-27B-F16.gguf
spec-type = draft-mtp
spec-draft-n-max = 2 # ⚠ if the load log says this file has no MTP head, comment the two
# spec- lines out — and expect ~14 t/s, probably not worth keeping.
ctx-size = 24576 # RESTORED 2026-09-10 (16384 was the 08-17 gate-bench trim): matches
# the clients. KV is cheap (16 full-attn layers × 4 kv-heads × 256
# head_dim ≈ 26 KB/token at q8/q4): 16k→24k cost +0.3 → MEASURED
# 09-10 @1.6 baseline: 14.6 used / 2.5 free. n_ctx_train 262144.
n-gpu-layers = 99 # dense: no n-cpu-moe escape — ctx/quant are the only knobs
threads = 6
flash-attn = on
cache-type-k = q8_0
cache-type-v = q4_0 # hybrid arch takes mixed KV like Coder-Next (GLM's same-type
# rejection was MLA-specific). If load fails at context creation:
# try v = q8_0, then delete both lines (f16 — KV small enough here).
batch-size = 2048
ubatch-size = 256 # KEPT at 256 (512 before the 08-17 trim): prefill is GDN-bound on the
# Vulkan backend (30-34 t/s either way), so the bigger compute buffer
# buys nothing measurable and VRAM is the binding constraint here.
cache-reuse = 256
defrag-thold = 0.1
no-mmproj-offload = true # vision encoder on CPU → ~1 GB freed, same as the 26B
temp = 1.0 # card: thinking mode 1.0/0.95/20/0 — thinking IS the default mode
top-p = 0.95
top-k = 20
min-p = 0
jinja = on
sleep-idle-seconds = 120
# ─── 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
# quant would cost the quality that justified it. Removed from opencode/pi/aichat.
@@ -0,0 +1,53 @@
# dunst drop-in — undo HyDE's pink critical notification, and make the progress bar visible.
#
# Why a drop-in and not the obvious files:
# ~/.config/dunst/dunstrc is GENERATED by ~/.local/share/wallbash/scripts/dunst.sh on every
# theme change, and says so in its own header.
# ~/.config/dunst/dunst.conf is HyDE's shipped source for that generator — but dunst.toml
# declares `action = "sync"` on the whole `dunst` directory, so HyDE
# overwrites it on every update.
# dunst >= 1.10 reads dunstrc.d/*.conf as drop-ins that override the base config (lexically last
# wins), and HyDE's sync copies its files in without deleting extras — so this file survives both
# a theme change and a HyDE update. Apply with `dunstctl reload`.
#
# [urgency_critical] is the pink: HyDE's generator injects background #f5e0dc (Catppuccin
# rosewater) with a #f38ba8 frame — a pale pink slab, and with `timeout = 0` it never expires, so
# one error notification sits on screen until dismissed. Note this block is NOT wallbash-driven
# (dunst.conf ships it commented out and the generator hardcodes it), so overriding it costs no
# theme coupling. Inverted here: dark base like every other surface, with the pink kept as text
# and frame so "critical" still reads as critical at a glance.
[urgency_critical]
background = "#1e1e2eF2"
foreground = "#f5e0dc"
frame_color = "#f38ba8"
# urgency_low/normal are the everyday notifications — the volume/mute popup is `normal`, since
# volumecontrol.sh sends it with no -u. wallbash generates those as a primary colour at alpha `80`
# (#583A6B80 plum for normal, #657AA380 for low) and [global] adds `transparency = 10` on top, so
# roughly half the wallpaper shows through: on a pink-toned palette the popup reads as pink no
# matter what the base hue is.
#
# Overriding them here **decouples notifications from wallbash** — they no longer follow the
# wallpaper the way the cava bar, waybar and kitty still do. That is the deliberate trade for a
# readable popup. To hand them back to wallbash, delete these two blocks and restart dunst; the
# generated dunstrc underneath is untouched.
#
# The frames also needed fixing regardless of colour: the generator emits frame_color at alpha
# `03` (~1% opacity) and never sets `highlight`, and dunst falls back to frame_color for the
# progress bar — so the volume bar was drawing invisibly.
#
# Frame and highlight are deliberately NOT the mauve from hypr's active window border: at these
# sizes #ca9ee6 renders as a pink outline plus a pink bar, which is the thing that looked wrong in
# the first place. A neutral surface frame with a blue bar keeps the popup quiet.
[urgency_low]
background = "#1e1e2eF2"
foreground = "#cdd6f4"
frame_color = "#6c7086"
[urgency_normal]
background = "#1e1e2eF2"
foreground = "#cdd6f4"
frame_color = "#45475a"
[global]
highlight = "#89b4fa"
@@ -0,0 +1,26 @@
## gui package — pin which config Hyprland starts with.
##
## HyDE 26.x configures Hyprland in Lua and points HYPRLAND_CONFIG at
## ~/.local/share/hypr/hyde.lua. Once that is set, Hyprland never reads
## ~/.config/hypr/hyprland.conf, so the entire dots hypr layer (theme, keybinds,
## window rules, monitors, userprefs) is bypassed and the session comes up as
## stock HyDE — silently, with `hyprctl configerrors` empty.
##
## Why it is set from *fish* of all places: the update dropped a real
## ~/.config/fish/conf.d/hyde.fish (untracked, HyDE-owned) which exports it, and
## fish sources every conf.d/*.fish at startup. SDDM launches the session through
## the login shell (/usr/bin/start-hyprland, the `hyprland.desktop` entry — not
## `hyprland-uwsm.desktop`), so the compositor inherits whatever fish exported.
## 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
## 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
## covers the `hyprland-uwsm.desktop` session entry, which sources uwsm env files
## instead of the shell. Both are needed; neither is sufficient alone.
##
## STOPGAP: this pins ~/.local/share/hyde/hyprland.conf, which current HyDE no
## longer ships (ours is a leftover of the pre-update install). The supported
## path is porting the hypr layer to ~/.config/hypr/hyprland.lua. See
## HYDE-UPDATE.md. Takes effect at session start — relogin, not `hyprctl reload`.
set -gx HYPRLAND_CONFIG "$HOME/.config/hypr/hyprland.conf"
@@ -0,0 +1,25 @@
## gui package — undo the starship prompt that HyDE's fish snippet installs.
##
## The HyDE 26.x update dropped an untracked ~/.config/fish/conf.d/hyde.fish that
## runs `starship init fish | source`. That eagerly defines fish_prompt AND
## fish_right_prompt, which shadows our own functions/fish_prompt.fish: fish only
## autoloads a function that is not already defined, so ours never loaded and
## every new shell came up with starship's prompt.
##
## conf.d is sourced alphabetically, so this file must sort AFTER hyde.fish to
## undo it — hence zz-.
##
## Note it *sources* our prompts rather than erasing starship's: `functions --erase`
## on an autoloadable name also suppresses the autoload, which leaves the shell
## with no fish_prompt at all ("Unknown command: fish_prompt"). Defining ours over
## the top is what actually wins — for fish_right_prompt too, since common ships
## functions/fish_right_prompt.fish (the git-status right prompt).
##
## Nothing else from hyde.fish is undone here: its aliases lose to config.fish,
## which fish loads after conf.d, and its `df` wrapper is a function we do not
## otherwise define. Delete this file if you ever decide you want starship.
for f in fish_prompt fish_right_prompt
if test -r $__fish_config_dir/functions/$f.fish
source $__fish_config_dir/functions/$f.fish
end
end
-1
View File
@@ -1 +0,0 @@
source = ./HyDE.conf
+26 -6
View File
@@ -27,9 +27,18 @@
# █▀█ █▀▀ █▀▀ ▄█
# $QUICKAPPS = # used for quick app launcher
# $BROWSER = firefox # default browser, if commented out , will use the default browser
# 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
# $EXPLORER= dolphin # default file manager, if commented out , will use the default file manager
# 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
@@ -43,14 +52,21 @@
# $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
# $start.BAR=waybar
# 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/swwwallpaper.sh
# $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
# $start.NETWORK_MANAGER=nm-applet --indicator
# 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
@@ -104,7 +120,11 @@
#$GTK_THEME=Wallbash-Gtk
#$ICON_THEME=Tela-circle-dracula
# 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
# // █▀▀ █░█ █▀█ █▀ █▀█ █▀█
-132
View File
@@ -1,132 +0,0 @@
# ░▒▒▒░░░░░▓▓ ___________
# ░░▒▒▒░░░░░▓▓ //___________/
# ░░▒▒▒░░░░░▓▓ _ _ _ _ _____
# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/
# ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___
# ░▒▒░░▓▓ ▓▓ \__ |____/ |____/
# ░▒▓▓ ▓▓ //____/
$fontFamily = JetBrainsMono Nerd Font # We already have this font installed
# Resolving custom fonts
# Provide the font name and the download link separated by a pipe |
# Run font.sh resolve $LAYOUT_PATH to install the font
$resolve.font=Anurati|https://font.download/dl/font/anurati.zip
$resolve.font=Inter|https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip
background {
monitor =
color = $wallbash_pry1_rgba
path = $BACKGROUND_PATH
blur_size = 4
blur_passes = 3 # 0 disables blurring
noise = 0.0117
contrast = 1.3000 # Vibrant!!!
brightness = 0.8000
vibrancy = 0.2100
vibrancy_darkness = 0.0
}
# DAY
label {
monitor =
text = cmd[update:1000] echo "$(date +"%A" | sed 's/./&/g' | tr '[:lower:]' '[:upper:]')" # Add a thin space between each character
color = $wallbash_1xa9_rgba
font_size = 110 # Wednesday is too long
font_family = Anurati
position = 0, 100
halign = center
valign = center
}
# DATE
label {
monitor =
text = cmd[update:1000] echo "$(date +"%B %d")"
color = $wallbash_2xa9_rgba
font_size = 35
font_family = $font_family Thin
position = 0, -20
halign = center
valign = center
}
# TIME
label {
monitor =
text = cmd[update:1000] echo "$(date +"%-I:%M %p")"
#text = $TIME
color = $wallbash_txt1_rgba
font_size = 20 #!
font_family = $font_family
position = 0, 150
halign = center
valign = bottom
}
# INPUT FIELD
input-field {
monitor =
size = 200, 50 #!
outline_thickness = 3
dots_size = 0.33 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.15 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
dots_rounding = -1 # -1 default circle, -2 follow input-field rounding
outer_color = $wallbash_pry4_rgba
inner_color = $wallbash_pry2_rgba
font_color = $wallbash_3xa9_rgba
fade_on_empty = true
fade_timeout = 1000 # Milliseconds before fade_on_empty is triggered.
placeholder_text = <i>Input Password...</i> # Text rendered in the input box when it's empty.
hide_input = false
rounding = -1 # -1 means complete rounding (circle/oval)
check_color = $wallbash_pry4_rgba
fail_color = rgba(FF0000FF) # if authentication failed, changes outer_color and fail message color
fail_text = <i>$FAIL <b>($ATTEMPTS)</b></i> # can be set to empty
fail_transition = 300 # transition time in ms between normal outer_color and fail_color
capslock_color = -1
numlock_color = -1
bothlock_color = -1 # when both locks are active. -1 means don't change outer color (same for above)
invert_numlock = false # change color if numlock is off
swap_font_color = true # see below
position = 0, 80
halign = center
valign = bottom
}
#User tag
label {
monitor =
text =  $USER
color = $wallbash_4xa9_rgba
font_size = 20
font_family = Inter Display Medium
position = 0, 30
halign = center
valign = bottom
}
# Battery Status if present
label {
monitor =
text = cmd[update:5000] $BATTERY_ICON
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrainsMono Nerd Font
position = -1%, 1%
halign = right
valign = bottom
}
# Current Keyboard Layout
label {
monitor =
text = cmd[update:1000] $KEYBOARD_LAYOUT
color = $wallbash_4xa9_rgba
font_size = 20
font_family = Inter Display Medium
position = -2%, 1%
halign = right
valign = bottom
}
@@ -1,158 +0,0 @@
# Hyprclouds from Arfan on Clouds
# By: Arfan on Clouds https://github.com/arfan-on-clouds/hyprclouds
# Modified by: The HyDE Project
$resolve.font=SF Pro Display|https://font.download/dl/font/sf-pro-display.zip
$resolve.font=AlfaSlabOne|https://font.download/dl/font/alfa-slab-one.zip
# BACKGROUND
background {
monitor =
color = $wallbash_pry1_rgba
path = $BACKGROUND_PATH
blur_size = 4
blur_passes = 3 # 0 disables blurring
noise = 0.0117
contrast = 1.3000 # Vibrant!!!
brightness = 0.8000
vibrancy = 0.2100
vibrancy_darkness = 0.0
}
# INPUT FIELD
input-field {
monitor =
size = 190, 50
outline_thickness = 2
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
fade_on_empty = true
font_family = SF Pro Display Bold
placeholder_text = <i><span foreground="##ffffff99"></span></i>
hide_input = false
position = 0, -170
halign = center
valign = center
outer_color = $wallbash_pry4_rgba
inner_color = $wallbash_pry2_rgba
font_color = $wallbash_3xa9_rgba
check_color = $wallbash_pry4_rgba
}
# Hour-Time
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%I")"
color = $wallbash_1xa8_rgba
shadow_passes = 3
shadow_size = 2
font_size = 100
font_family = AlfaSlabOne
position = 0, 100
halign = center
valign = center
}
# Minute-Time
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%M")"
color = $wallbash_3xa9_rgba
font_size = 100
font_family = AlfaSlabOne
shadow_passes = 3
shadow_size = 2
position = 0, -20
halign = center
valign = center
}
# Day-Date-Month
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%d %b %A")"
color = $wallbash_3xa9_rgba
shadow_passes = 3
shadow_size = 1
font_size = 12
font_family = JetBrains Mono Nerd Font Mono ExtraBold
position = 1%, -1%
halign = left
valign = top
}
# CURRENT SONG
label {
monitor =
text = cmd[update:1000] $MPRIS_TEXT # Outputs the song title when mpris is available, otherwise, it will output the splash command.
color = $wallbash_3xa9_rgba
shadow_passes = 3
shadow_size = 1
font_size = 14
font_family = JetBrains Mono Nerd, SF Pro Display Bold
position = 0, 20
halign = center
valign = bottom
}
image {
monitor =
path = $MPRIS_IMAGE
size = 80 # lesser side if not 1:1 ratio
rounding = -1 # negative values mean circle
border_size = 0
shadow_passes = 3
shadow_size = 3
border_color = rgb(221, 221, 221)
rotate = 0 # degrees, counter-clockwise
reload_time = 0 # seconds between reloading, 0 to reload with SIGUSR2
# reload_cmd = # command to get new path. if empty, old path will be used. don't run "follow" commands like tail -F
position = 0, 10%
halign = center
valign = bottom
}
# Battery Status if present
label {
monitor =
text = cmd[update:5000] $BATTERY_ICON
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrainsMono Nerd Font
position = -1%, 1%
halign = right
valign = bottom
}
# Current Keyboard Layout
label {
monitor =
text = cmd[update:1000] $KEYBOARD_LAYOUT
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrains Mono Nerd, SF Pro Display Bold
position = -2%, 1%
halign = right
valign = bottom
}
# Weather
#! Put the weather command last to lessen the load time of other modules
label {
monitor =
text = cmd[update:18000000] $WEATHER_CMD
color = $wallbash_3xa9_rgba
font_size = 16
shadow_passes = 3
shadow_size = 1
font_family = JetBrains Mono Nerd, SF Pro Display Bold
position = -1%, -1%
halign = right
valign = top
}
-112
View File
@@ -1,112 +0,0 @@
# ░▒▒▒░░░░░▓▓ ___________
# ░░▒▒▒░░░░░▓▓ //___________/
# ░░▒▒▒░░░░░▓▓ _ _ _ _ _____
# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/
# ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___
# ░▒▒░░▓▓ ▓▓ \__ |____/ |____/
# ░▒▓▓ ▓▓ //____/
$fontFamily = IBM Plex Sans
$resolve.font=IBM Plex Sans|https://github.com/IBM/plex/releases/download/%40ibm%2Fplex-sans%401.1.0/ibm-plex-sans.zip
# GENERAL
background {
monitor =
path = $BACKGROUND_PATH
blur_size = 5
blur_passes = 0
noise = 0.0117
contrast = 1.3000 # Vibrant!
brightness = 0.8000
vibrancy = 0.2100
vibrancy_darkness = 0.0
}
# Current time
label {
monitor =
text = cmd[update:1000] echo "<b><big> $(date +"%H") </big></b>"
color = $wallbash_2xa7_rgba
font_size = 180
font_family = $fontFamily Medium 10
position = 0, 80
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] echo "<b><big> $(date +"%M") </big></b>"
color = $wallbash_2xa9_rgba
font_size = 180
font_family = $fontFamily Medium 10
position = 0, -120
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] echo "<b><big> $(date +"%d %b") </big></b>"
color = $wallbash_2xa8_rgba
font_size = 20
font_family = $fontFamily Medium 10
position = 0, -230
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] echo "<b><big> $(date +"%A") </big></b>"
color = $wallbash_2xa8_rgba
font_size = 20
font_family = $fontFamily Medium 10
position = 0, -250
halign = center
valign = center
}
#INPUT FIELD
input-field {
monitor =
# size = 15%, 5% # hyprlock-git
size = 200, 50
outline_thickness = 3
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 1.00 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
outer_color = $wallbash_pry2_rgba
inner_color = $wallbash_pry3_rgba
font_color = $color7
fade_on_empty = true
placeholder_text = <i>Password...</i> # Text rendered in the input box when it's empty.
hide_input = false
position = 0, 60
halign = center
valign = bottom
}
# Battery Status if present
label {
monitor =
text = cmd[update:5000] $BATTERY_ICON
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrainsMono Nerd Font
position = -1%, 1%
halign = right
valign = bottom
}
# Current Keyboard Layout
label {
monitor =
text = cmd[update:1000] $KEYBOARD_LAYOUT
color = $wallbash_4xa9_rgba
font_size = 20
font_family = $fontFamily
position = -2%, 1%
halign = right
valign = bottom
}
-161
View File
@@ -1,161 +0,0 @@
# Hyprlock style-4
# By: Vivek Rajan https://github.com/MrVivekRajan/Hyprlock-Styles/tree/main/Style-4
# Modified by: The HyDE Project
$resolve.font=SF Pro Display|https://font.download/dl/font/sf-pro-display.zip
# BACKGROUND
background {
monitor =
path = $BACKGROUND_PATH
blur_passes = 0
contrast = 0.8916
brightness = 0.8916
vibrancy = 0.8916
vibrancy_darkness = 0.0
}
# GENERAL
general {
no_fade_in = false
grace = 0
disable_loading_bar = false
}
# Profile-Photo
image {
monitor =
path = $PROFILE_IMAGE
border_size = 2
border_color = $wallbash_pry4_rgba
size = 100
rounding = -1
rotate = 0
reload_time = -1
reload_cmd =
position = 25, 200
halign = center
valign = center
}
# NAME
label {
monitor =
text = cmd[update:60000] $GREET_TEXT
color = $wallbash_txt1_rgba
outline_thickness = 0
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
font_size = 20
font_family = SF Pro Display Bold
position = 25, 110
halign = center
valign = center
}
# Time
label {
monitor =
text = cmd[update:1000] echo "<span>$(date +"%I:%M")</span>"
color = $wallbash_txt1_rgba
font_size = 60
font_family = SF Pro Display Bold
position = 30, -8
halign = center
valign = center
}
# Day-Month-Date
label {
monitor =
text = cmd[update:1000] echo -e "$(date +"%A, %B %d")"
color = $wallbash_txt1_rgba
font_size = 19
font_family = SF Pro Display Bold
position = 35, -60
halign = center
valign = center
}
# USER-BOX
shape {
monitor =
size = 320, 55
color = rgba(255, 255, 255, 0.1)
rounding = -1
border_size = 0
border_color = $wallbash_txt3_rgba
rotate = 0
xray = false # if true, make a "hole" in the background (rectangle of specified size, no rotation)
position = 34, -190
halign = center
valign = center
}
# USER
label {
monitor =
text =  $USER
color = $wallbash_1xa9_rgba
outline_thickness = 0
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
font_size = 16
font_family = SF Pro Display Bold
position = 38, -190
halign = center
valign = center
}
# INPUT FIELD
input-field {
monitor =
size = 320, 55
outline_thickness = 0
dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
outer_color = rgba(255, 255, 255, 0)
inner_color = rgba(255, 255, 255, 0.1)
font_color = $wallbash_2xa6_rgba
fade_on_empty = false
font_family = SF Pro Display Bold
placeholder_text = <i><span foreground="##ffffff99">🔒 Enter Pass</span></i>
hide_input = false
position = 34, -268
halign = center
valign = center
check_color = $wallbash_txt4_rgba
fail_color = rgb(204, 34, 34) # if authentication failed, changes outer_color and fail message color
fail_text = <i>$FAIL <b>($ATTEMPTS)</b></i> # can be set to empty
fail_transition = 300 # transition time in ms between normal outer_color and fail_color
}
# Battery Status if present
label {
monitor =
text = cmd[update:5000] $BATTERY_ICON
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrainsMono Nerd Font
position = -1%, 1%
halign = right
valign = bottom
}
# Current Keyboard Layout
label {
monitor =
text = cmd[update:1000] $KEYBOARD_LAYOUT
color = $wallbash_4xa9_rgba
font_size = 20
font_family = SF Pro Display Bold
position = -2%, 1%
halign = right
valign = bottom
}
-1
View File
@@ -1 +0,0 @@
source = ./HyDE.conf
+8 -4
View File
@@ -26,15 +26,19 @@
$wm=Window Management
$d=[$wm]
bindd = $mainMod, Q, $d close focused window, exec, $scrPath/dontkillsteam.sh
bindd = Alt, F4, $d close focused window, exec, $scrPath/dontkillsteam.sh
# `dontkillsteam.sh` was dropped by HyDE (gone from lib/hyde as of the 2026-08-13 update) and
# upstream now closes with the native dispatcher — its Lua key_binds.lua binds both of these to
# `hl.dsp.window.close()`. A bind pointing at a missing script stays valid and silently does
# nothing, which on these two keys means windows stop closing.
bindd = $mainMod, Q, $d close focused window, killactive,
bindd = Alt, F4, $d close focused window, killactive,
bindd = $mainMod, Delete, $d kill hyprland session, exit
bindd = $mainMod, W, $d toggle float, togglefloating, #
bindd = $mainMod Shift, G, $d toggle group, togglegroup
bindd = Shift, F11, $d toggle fullscreen, fullscreen
bindd = Alt, Return, $d toggle fullscreen, fullscreen
bindd = $mainMod, L, $d lock screen, exec, lockscreen.sh
bindd = $mainMod Shift, F, $d toggle pin on focused window, exec, $scrPath/window.pin.sh
bindd = $mainMod Shift, F, $d toggle pin on focused window, exec, $scrPath/windowpin.sh # renamed upstream (was window.pin.sh); the old machine-local shim is gone with the update
bindd = Control Alt, Delete, $d logout menu, exec, $scrPath/logoutlaunch.sh
bindd = Control Alt, W, $d toggle waybar, exec, killall waybar || waybar -c ~/.config/waybar/layouts/custom.jsonc -s ~/.config/waybar/layouts/custom.css
# bindd = Control Alt, W, $d toggle waybar, exec, hyde-shell waybar --hide
@@ -123,7 +127,7 @@ bindd = $mainMod, B, $d web browser , exec, $BROWSER
bindd = $mainMod Shift, B, $d web browser , exec, librewolf
bindd = $mainMod Control, B, $d web browser , exec, firefox
bindd = $mainMod, Y, $d ferdium, exec, ferdium
bindd = Control Shift, Escape, $d system monitor , exec, $scrPath/sysmonlaunch.sh
bindd = Control Shift, Escape, $d system monitor , exec, $scrPath/system.monitor.sh # renamed upstream (was sysmonlaunch.sh)
bindd = $mainMod, F, $d freetube , exec, freetube # youtube alternative
bindd = $mainMod, K, $d password manager , exec, keepassxc # launch password manager
bindd = $mainMod, G, $d xmpp, exec, gajim # launch messanger
+2
View File
@@ -29,6 +29,8 @@ layerrule = ignore_alpha 0,match:namespace cavabar
env = SSH_AUTH_SOCK,$XDG_RUNTIME_DIR/ssh-agent.socket # ssh on start
env = ELECTRON_OZONE_PLATFORM_HINT,x11 # Disable after hyprland fixes electron flickering
exec-once = waybar -c ~/.config/waybar/layouts/custom.jsonc -s ~/.config/waybar/layouts/custom.css
# Steam Controller -> Deck-style binds. Not a systemd user unit: graphical-session.target
exec-once = scc-daemon start
# (No static `noanim` layerrule: current Hyprland rejects it in the match: syntax, and
# grimblast already suppresses selection animation at runtime via `hyprctl keyword layerrule`.)
+14
View File
@@ -0,0 +1,14 @@
# run with `kitty --session ~/.config/kitty/c2.conf`
# create a new tab
new_tab c2
cd ~/projects/wingman/wm-clients/c2/c2-main/
launch --var window=first nvim
# open the session focused on nvim
focus
new_tab git
cd ~/projects/wingman/wm-clients/c2/c2-main/
launch --var window=second lazygit
+25
View File
@@ -0,0 +1,25 @@
# run with `kitty --session ~/.config/kitty/runners.conf`
##### TAB Logs #####
new_tab logs
layout splits
cd ~/projects/wingman/wm-clients/c2/c2-main/
# --hold keeps the window open if the process crashes
launch --var window=first --hold pnpm dev
launch --location=hsplit lazydocker
# resize_window wider 120
##### TAB Runners #####
new_tab runners
layout splits
launch --var window=first react-devtools
cd ~/software/wmclient/
launch --location=hsplit --hold ./wmsimulator.sh
resize_window taller 120
+46 -29
View File
@@ -1,61 +1,78 @@
## name: Catppuccin Mocha 🌿
## author: Pocco81 (https://github.com/Pocco81)
## license: MIT
## upstream: https://github.com/catppuccin/kitty/blob/main/mocha.conf
## blurb: Soothing pastel theme for the high-spirited!
# The basic colors
foreground #ffffff
background #262335
selection_foreground #262335
selection_background #f97e72
selection_foreground #1E1E2E
selection_background #F5E0DC
# Cursor colors
cursor #f97e72
cursor_text_color #262335
cursor #F5E0DC
cursor_text_color #1E1E2E
# URL underline color when hovering with mouse
url_color #f97e72
url_color #B4BEFE
# Kitty window border colors
active_border_color #CBA6F7
inactive_border_color #8E95B3
bell_border_color #EBA0AC
# OS Window titlebar colors
wayland_titlebar_color system
macos_titlebar_color system
# Tab bar colors
active_tab_foreground #11111B
active_tab_background #CBA6F7
inactive_tab_foreground #CDD6F4
inactive_tab_background #181825
# Colors for marks (marked text in the terminal)
mark1_foreground #262335
mark1_background #614D85
mark2_foreground #262335
mark2_background #614D85
mark3_foreground #262335
mark3_background #614D85
mark1_foreground #1E1E2E
mark1_background #87B0F9
mark2_foreground #1E1E2E
mark2_background #CBA6F7
mark3_foreground #1E1E2E
mark3_background #74C7EC
# The 16 terminal colors
# black
color0 #232530
# bright-black = dim/secondary text (menus, comments); was #232530 ≈ background so it
# rendered invisible. Lifted to the theme's muted lavender for legible dim text.
color8 #848bbd
color0 #43465A
color8 #43465A
# red
color1 #fe4450
color9 #fe4450
color1 #F38BA8
color9 #F38BA8
# green
color2 #72f1b8
color10 #72f1b8
color2 #A6E3A1
color10 #A6E3A1
# yellow
color3 #ff7edb
color11 #ff7edb
color3 #F9E2AF
color11 #F9E2AF
# blue
color4 #03edf9
color12 #03edf9
color4 #87B0F9
color12 #87B0F9
# magenta
color5 #fede5d
color13 #f3e70f
color5 #F5C2E7
color13 #F5C2E7
# cyan
color6 #03edf9
color14 #03edf9
color6 #94E2D5
color14 #94E2D5
# white
color7 #ffffff
color15 #ffffff
color7 #CDD6F4
color15 #A1A8C9
@@ -0,0 +1,28 @@
#!/usr/bin/env sh
# Keep Hyprland on this repo's hyprlang config instead of HyDE's Lua one.
#
# HyDE 26.x moved its Hyprland config to Lua. Its own `00-hyde.sh` (synced on
# every update) points HYPRLAND_CONFIG at ~/.local/share/hypr/hyde.lua, and
# Hyprland then never reads ~/.config/hypr/hyprland.conf — so every dots hypr
# file (theme, keybinds, window rules, monitors, userprefs) is silently bypassed
# and the session comes up as stock HyDE: no rounding/gaps/borders, HyDE's binds,
# HyDE's plain waybar instead of the cava rig. Nothing errors; `hyprctl
# configerrors` stays empty. The tell is the log's first lines:
# [cfg] Config is either explicit or special. / [cfg] Config is lua, loading lua mgr
#
# `00-hyde.sh` assigns with `${HYPRLAND_CONFIG:-...}` and `env-hyprland` sources
# `env-hyprland.d/*.sh` in glob order, so this file (10 sorts after 00) wins by
# assigning unconditionally. Session-start only — this needs a relogin, not a
# `hyprctl reload`. HyDE syncs the whole env-hyprland.d/ directory but only ever
# writes its own files into it, so this one survives updates.
#
# ── STOPGAP, not the destination ──────────────────────────────────────────────
# It pins ~/.local/share/hyde/hyprland.conf, which current HyDE no longer ships:
# ours is a leftover from the pre-update install, so nothing regenerates it. And
# 8 scripts our binds reach for through $scrPath are already gone from the new
# lib/hyde — including dontkillsteam.sh, which is bound to Super+Q and Alt+F4.
# The supported path is porting the hypr layer into ~/.config/hypr/hyprland.lua
# (the one file HyDE promises never to overwrite). See HYDE-UPDATE.md.
HYPRLAND_CONFIG="$HOME/.config/hypr/hyprland.conf"
export HYPRLAND_CONFIG
+1 -1
View File
@@ -8,7 +8,7 @@
# child and they'd pile up. We enforce a single instance: track cava's own PID,
# kill it on exit, and reap any stale instance left by a previous (crashed) run.
bars="${1:-94}"
chars="▁▂▃▄▅▆▇█" # cava ascii value 0..7 -> these
chars="▁▂▃▄▅▆▇█" # cava ascii value 0..7 -> these
conf="$(mktemp)"
pidfile="${XDG_RUNTIME_DIR:-/tmp}/cava-waybar.pid"
-332
View File
@@ -1,332 +0,0 @@
// --// standalone two-bar cava rig hand-maintained, NOT HyDE-generated (see README "Waybar") //-- //
[
{
// sourced from header module //
"layer": "top",
"position": "top",
"height": 33,
"exclusive": true,
"passthrough": false,
"gtk-layer-shell": true,
"reload_style_on_change": true,
// positions generated based on config.ctl //
"modules-left": ["custom/l_end","custom/power","custom/r_end","hyprland/workspaces", "custom/sl_end","hyprland/window","custom/sr_end","custom/l_end","custom/wallchange","custom/cliphist","custom/r_end"],
"modules-center": ["wlr/taskbar"],
"modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"],
// sourced from modules based on config.ctl //
"tray": {
"icon-size": 19,
"rotate": 0,
"spacing": 5
},
// NOTE: keep in sync with modules/window.jsonc this layout is launched standalone
// (waybar -c), so the modules/ file is NOT read here; this inline copy is what renders.
"hyprland/window": {
"format": " {}",
"rotate": 0,
"separate-outputs": true,
"max-length": 1000,
"rewrite": {
"(.*) — Kitty": " $1",
"(.*)~": " $1",
"(.*) — Mozilla Firefox": "󰈹 $1",
"(.*)Mozilla Firefox": "󰈹 Firefox",
"(.*) — LibreWolf": "󰈹 $1",
"(.*)LibreWolf": "󰈹 LibreWolf",
"(.*) — Chromium": " $1",
"(.*)Chromium": " Chromium",
"(.*) - VSCodium": "󰨞 $1",
"(.*)VSCodium": "󰨞 VSCodium",
"(.*)Code - OSS": "󰨞 $1",
"(.*) - Visual Studio Code": "󰨞 $1",
"(.*)Visual Studio Code": "󰨞 Code",
"(.*) — Dolphin": "󰉋 $1",
"(.*) - Obsidian(.*)": "󰠮 $1",
"(.*)Ferdium": "󰭻 Ferdium",
"(.*)FreeTube": " FreeTube",
"(.*) - VLC media player": "󰕼 $1",
"(.*)Steam": "󰓓 Steam",
"(.*)Neovide": " Nvim",
"(.*)Neovim": " Nvim",
"(.*)Nvim": " Nvim",
"(.*)Gajim": "󰭹 Gajim",
"(.*)KeePassXC": " KeePassXC",
"(.*)Betterbird": "󰇮 Betterbird",
"(.*)Spotify": "󰓇 Spotify"
}
},
"hyprland/workspaces": {
"window-rewrite-default":"",
"all-outputs": true,
"active-only": false,
"on-click": "activate",
"disable-scroll": false,
"on-scroll-up": "hyprctl dispatch workspace -1",
"on-scroll-down": "hyprctl dispatch workspace +1",
"persistent-workspaces": {}
},
"cpu": {
"interval": 10,
"format": "󰍛 {usage}%",
"format-alt": "{icon0}{icon1}{icon2}{icon3}",
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
},
"custom/cpuinfo": {
"exec": " cpuinfo.sh",
"return-type": "json",
"format": "{}",
"rotate": 0,
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000
},
"memory": {
"states": {
"c": 90, // critical
"h": 60, // high
"m": 30 // medium
},
"interval": 30,
"rotate": 0,
"format": "󰾆 {used:0.1f}GB",
"format-m": "󰾅 {used:0.1f}GB",
"format-h": "󰓅 {used:0.1f}GB",
"format-c": " {used:0.1f}GB",
"format-alt": "󰾆 {percentage}%",
"max-length": 10,
"tooltip": true,
"tooltip-format": "󰾆 {percentage}%\n {used:0.1f}GB/{total:0.1f}GB"
},
"custom/power": {
"format": "{}",
"rotate": 0,
"exec": "echo ; echo  logout",
"on-click": "logoutlaunch.sh 2",
"on-click-right": "logoutlaunch.sh 1",
"interval" : 86400, // once every day
"tooltip": true
},
"wlr/taskbar": {
"format": "{icon}",
"rotate": 0,
"icon-size": 19,
"icon-theme": "Tela-circle-dracula",
"spacing": 0,
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close",
"ignore-list": [
"Alacritty"
],
"app_ids-mapping": {
"firefoxdeveloperedition": "firefox-developer-edition"
}
},
"idle_inhibitor": {
"format": "{icon}",
"rotate": 0,
"format-icons": {
"activated": "󰅶",
"deactivated": "󰛊"
}
},
"clock": {
"format": "{:%H:%M}",
"rotate": 0,
"format-alt": "{:%R 󰃭 %d·%m·%y}",
"tooltip-format": "<span>{calendar}</span>",
"calendar": {
"mode": "month",
"mode-mon-col": 3,
"on-scroll": 1,
"on-click-right": "mode",
"format": {
"months": "<span color='#ffead3'><b>{}</b></span>",
"weekdays": "<span color='#ffcc66'><b>{}</b></span>",
"today": "<span color='#ff6699'><b>{}</b></span>"
}
},
"actions": {
"on-click-right": "mode",
"on-click-forward": "tz_up",
"on-click-backward": "tz_down",
"on-scroll-up": "shift_up",
"on-scroll-down": "shift_down"
}
},
"custom/cliphist": {
"format": "{}",
"rotate": 0,
"exec": "echo ; echo 󰅇 clipboard history",
"on-click": "sleep 0.1 && cliphist.sh c",
"on-click-right": "sleep 0.1 && cliphist.sh d",
"on-click-middle": "sleep 0.1 && cliphist.sh w",
"interval" : 86400, // once every day
"tooltip": true
},
"custom/wallchange": {
"format": "{}",
"rotate": 0,
"exec": "echo ; echo 󰆊 switch wallpaper",
"on-click": "swwwallpaper.sh -n",
"on-click-right": "swwwallpaper.sh -p",
"on-click-middle": "sleep 0.1 && swwwallselect.sh",
"interval" : 86400, // once every day
"tooltip": true
},
"pulseaudio": {
"format": "{icon} {volume}",
"rotate": 0,
"format-muted": "婢",
"on-click": "pavucontrol -t 3",
"on-click-right": "volumecontrol.sh -s ''",
"on-click-middle": "volumecontrol.sh -o m",
"on-scroll-up": "volumecontrol.sh -o i",
"on-scroll-down": "volumecontrol.sh -o d",
"tooltip-format": "{icon} {desc} // {volume}%",
"scroll-step": 5,
"format-icons": {
"headphone": "",
"hands-free": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", "", ""]
}
},
"pulseaudio#microphone": {
"format": "{format_source}",
"rotate": 0,
"format-source": "",
"format-source-muted": "",
"on-click": "pavucontrol -t 4",
"on-click-middle": "volumecontrol.sh -i m",
"on-scroll-up": "volumecontrol.sh -i i",
"on-scroll-down": "volumecontrol.sh -i d",
"tooltip-format": "{format_source} {source_desc} // {source_volume}%",
"scroll-step": 5
},
"mpris": {
"format": "{player_icon} {dynamic}",
"rotate": 0,
"format-paused": "{status_icon} <i>{dynamic}</i>",
"player-icons": {
"default": "▶",
"mpv": "🎵"
},
"status-icons": {
"paused": ""
},
"max-length": 1000,
"interval": 1
},
"battery": {
"states": {
"good": 95,
"warning": 30,
"critical": 20
},
"format": "{icon} {capacity}%",
"rotate": 0,
"format-charging": " {capacity}%",
"format-plugged": " {capacity}%",
"format-alt": "{time} {icon}",
"format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]
},
"backlight": {
"device": "intel_backlight",
"rotate": 0,
"format": "{icon} {percent}%",
"format-icons": ["", "", "", "", "", "", "", "", ""],
"on-scroll-up": "brightnessctl set 1%+",
"on-scroll-down": "brightnessctl set 1%-",
"min-length": 6
},
// modules for padding //
"custom/l_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/r_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/sl_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/sr_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/rl_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/rr_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/padd": {
"format": " ",
"interval" : "once",
"tooltip": false
}
},
// full-width cava visualizer layered behind the main bar //
{
"name": "cavabar",
"layer": "bottom",
"position": "top",
"height": 36,
"margin-top": -33,
"exclusive": false,
"passthrough": true,
"modules-center": ["custom/cava"],
"custom/cava": {
"format": "{}",
"exec": "bash $HOME/.config/waybar/cava-waybar.sh $(cat $HOME/.config/waybar/cava.width 2>/dev/null || echo 94)", // per-host cava.width (bars, even; ~screen_px / (font-size*0.6)). gui default 94; wm overrides to 218
"restart-interval": 1,
"hide-empty-text": true
}
}
]
@@ -1,307 +0,0 @@
/*
This is an autogenerated file.
Do not edit this file directly.
This is modified to dynamically
change the border-radius which will
follow the border-radius of hyprland.
To parse this file, we use PT as a unit
to represent the border-radius.
This is stored separately to avoid
issues on parsing the border-radius
*/
/* ? Leaf shape */
window#waybar.top #leaf,
window#waybar.top #leaf button,
window#waybar.top #leaf menu,
window#waybar.top #leaf menuitem,
window#waybar.top #leaf tooltip,
window#waybar.bottom #leaf,
window#waybar.bottom #leaf button,
window#waybar.bottom #leaf menu,
window#waybar.bottom #leaf menuitem,
window#waybar.bottom #leaf tooltip {
border-radius: 10pt 0 10pt 0;
}
window#waybar.left #leaf,
window#waybar.left #leaf button,
window#waybar.left #leaf menu,
window#waybar.left #leaf menuitem,
window#waybar.left #leaf tooltip,
window#waybar.right #leaf,
window#waybar.right #leaf button,
window#waybar.right #leaf menu,
window#waybar.right #leaf menuitem,
window#waybar.right #leaf tooltip {
border-radius: 10pt 0 10pt 0;
}
/* ? Inverse Leaf shape */
window#waybar.top #leaf-inverse,
window#waybar.top #leaf-inverse button,
window#waybar.top #leaf-inverse menu,
window#waybar.top #leaf-inverse menuitem,
window#waybar.top #leaf-inverse tooltip,
window#waybar.bottom #leaf-inverse,
window#waybar.bottom #leaf-inverse button,
window#waybar.bottom #leaf-inverse menu,
window#waybar.bottom #leaf-inverse menuitem,
window#waybar.bottom #leaf-inverse tooltip {
border-radius: 0 10pt 0 10pt;
}
window#waybar.left #leaf-inverse,
window#waybar.left #leaf-inverse button,
window#waybar.left #leaf-inverse menu,
window#waybar.left #leaf-inverse menuitem,
window#waybar.left #leaf-inverse tooltip,
window#waybar.right #leaf-inverse,
window#waybar.right #leaf-inverse button,
window#waybar.right #leaf-inverse menu,
window#waybar.right #leaf-inverse menuitem,
window#waybar.right #leaf-inverse tooltip {
border-radius: 0 10pt 0 10pt;
}
/* ? Full Pill shape*/
window#waybar.bottom #pill menuitem,
window#waybar.bottom #pill menu,
window#waybar.bottom #pill tooltip,
window#waybar.bottom #pill,
window#waybar.bottom #pill button,
window#waybar.top #pill menuitem,
window#waybar.top #pill menu,
window#waybar.top #pill tooltip,
window#waybar.top #pill,
window#waybar.top #pill button {
border-radius: 10pt 10pt 10pt 10pt;
}
window#waybar.left #pill menuitem,
window#waybar.left #pill menu,
window#waybar.left #pill tooltip,
window#waybar.left #pill,
window#waybar.left #pill button,
window#waybar.right #pill menuitem,
window#waybar.right #pill menu,
window#waybar.right #pill tooltip,
window#waybar.right #pill,
window#waybar.right #pill button {
border-radius: 10pt 10pt 10pt 10pt;
}
/* ? half pill with the curve pointing dowmwards */
window#waybar.top #pill-down,
window#waybar.top #pill-down button,
window#waybar.top #pill-down menu,
window#waybar.top #pill-down menuitem,
window#waybar.top #pill-down tooltip,
window#waybar.bottom #pill-down,
window#waybar.bottom #pill-down button,
window#waybar.bottom #pill-down menu,
window#waybar.bottom #pill-down menuitem,
window#waybar.bottom #pill-down tooltip {
border-radius: 0 0 10pt 10pt;
}
window#waybar.left #pill-down,
window#waybar.left #pill-down button,
window#waybar.left #pill-down menu,
window#waybar.left #pill-down menuitem,
window#waybar.left #pill-down tooltip,
window#waybar.right #pill-down,
window#waybar.right #pill-down button,
window#waybar.right #pill-down menu,
window#waybar.right #pill-down menuitem,
window#waybar.right #pill-down tooltip {
border-radius: 0 0 10pt 10pt;
}
/*
? half pill with the curve pointing inwards
*/
window#waybar.top #pill-in,
window#waybar.top #pill-in button,
window#waybar.top #pill-in menu,
window#waybar.top #pill-in menuitem,
window#waybar.top #pill-in tooltip {
border-radius: 0 0 10pt 10pt;
}
window#waybar.right #pill-in,
window#waybar.right #pill-in button,
window#waybar.right #pill-in menu,
window#waybar.right #pill-in menuitem,
window#waybar.right #pill-in tooltip {
border-radius: 10pt 0 0 10pt;
}
window#waybar.bottom #pill-in,
window#waybar.bottom #pill-in button,
window#waybar.bottom #pill-in menu,
window#waybar.bottom #pill-in menuitem,
window#waybar.bottom #pill-in tooltip {
border-radius: 10pt 10pt 0 0;
}
window#waybar.left #pill-in,
window#waybar.left #pill-in button,
window#waybar.left #pill-in menu,
window#waybar.left #pill-in menuitem,
window#waybar.left #pill-in tooltip {
border-radius: 0 10pt 10pt 0;
}
/* ? half pill with the curve pointing to left */
window#waybar.top #pill-left,
window#waybar.top #pill-left button,
window#waybar.top #pill-left menu,
window#waybar.top #pill-left menuitem,
window#waybar.top #pill-left tooltip,
window#waybar.bottom #pill-left,
window#waybar.bottom #pill-left button,
window#waybar.bottom #pill-left menu,
window#waybar.bottom #pill-left menuitem,
window#waybar.bottom #pill-left tooltip {
border-radius: 10pt 0 0 10pt;
}
window#waybar.left #pill-left,
window#waybar.left #pill-left button,
window#waybar.left #pill-left menu,
window#waybar.left #pill-left menuitem,
window#waybar.left #pill-left tooltip,
window#waybar.right #pill-left,
window#waybar.right #pill-left button,
window#waybar.right #pill-left menu,
window#waybar.right #pill-left menuitem,
window#waybar.right #pill-left tooltip {
border-radius: 10pt 0 0 10pt;
}
/* ? half pill with the curve pointing to outwards */
window#waybar.top #pill-out,
window#waybar.top #pill-out button,
window#waybar.top #pill-out menu,
window#waybar.top #pill-out menuitem,
window#waybar.top #pill-out tooltip {
border-radius: 10pt 10pt 0 0;
}
window#waybar.right #pill-out,
window#waybar.right #pill-out button,
window#waybar.right #pill-out menu,
window#waybar.right #pill-out menuitem,
window#waybar.right #pill-out tooltip {
border-radius: 0 10pt 10pt 0;
}
window#waybar.bottom #pill-out,
window#waybar.bottom #pill-out button,
window#waybar.bottom #pill-out menu,
window#waybar.bottom #pill-out menuitem,
window#waybar.bottom #pill-out tooltip {
border-radius: 0 0 10pt 10pt;
}
window#waybar.left #pill-out,
window#waybar.left #pill-out button,
window#waybar.left #pill-out menu,
window#waybar.left #pill-out menuitem,
window#waybar.left #pill-out tooltip {
border-radius: 10pt 0 0 10pt;
}
/* ============================ */
/* ? half pill with the curve pointing to the right */
window#waybar.top #pill-right,
window#waybar.top #pill-right button,
window#waybar.bottom #pill-right menu,
window#waybar.bottom #pill-right menuitem,
window#waybar.bottom #pill-right tooltip,
window#waybar.bottom #pill-right,
window#waybar.bottom #pill-right button,
window#waybar.top #pill-right menu,
window#waybar.top #pill-right menuitem,
window#waybar.top #pill-right tooltip {
border-radius: 0 10pt 10pt 0;
}
window#waybar.left #pill-right,
window#waybar.left #pill-right button,
window#waybar.right #pill-right menu,
window#waybar.right #pill-right menuitem,
window#waybar.right #pill-right tooltip,
window#waybar.right #pill-right,
window#waybar.right #pill-right button,
window#waybar.left #pill-right menu,
window#waybar.left #pill-right menuitem,
window#waybar.left #pill-right tooltip {
border-radius: 0 10pt 10pt 0;
}
/* ? half pill with the flat surface pointing upwards */
window#waybar.top #pill-up,
window#waybar.top #pill-up button,
window#waybar.top #pill-up menu,
window#waybar.top #pill-up menuitem,
window#waybar.top #pill-up tooltip,
window#waybar.bottom #pill-up,
window#waybar.bottom #pill-up button,
window#waybar.bottom #pill-up menu,
window#waybar.bottom #pill-up menuitem,
window#waybar.bottom #pill-up tooltip {
border-radius: 10pt 10pt 0 0;
}
window#waybar.left #pill-up,
window#waybar.left #pill-up button,
window#waybar.left #pill-up menu,
window#waybar.left #pill-up menuitem,
window#waybar.left #pill-up tooltip,
window#waybar.right #pill-up,
window#waybar.right #pill-up button,
window#waybar.right #pill-up menu,
window#waybar.right #pill-up menuitem,
window#waybar.right #pill-up tooltip {
border-radius: 10pt 10pt 0 0;
}
tooltip {
border-radius: 10pt;
}
menu {
border-radius: 10pt;
}
menu menuitem {
border-radius: 10pt;
}
window.popup decoration {
border-radius: 10pt;
}
-14
View File
@@ -1,14 +0,0 @@
/*
Dynamic Style Configuration *
This is handled by HyDE
To generate a dynamic configuration
base on theme and user settings
*/
* {
border-radius: 0em;
font-family: "JetBrainsMono Nerd Font","JetBrainsMono Nerd Font";
font-size: 10px;
}
-120
View File
@@ -1,120 +0,0 @@
{
"include": [
"/home/anon/.config/waybar/modules/backlight.jsonc",
"/home/anon/.config/waybar/modules/bluetooth.jsonc",
"/home/anon/.config/waybar/modules/cava.jsonc",
"/home/anon/.config/waybar/modules/cliphist.jsonc",
"/home/anon/.config/waybar/modules/clock.jsonc",
"/home/anon/.config/waybar/modules/cpu.jsonc",
"/home/anon/.config/waybar/modules/cpuinfo.jsonc",
"/home/anon/.config/waybar/modules/footer.jsonc",
"/home/anon/.config/waybar/modules/github_hyprdots.jsonc",
"/home/anon/.config/waybar/modules/gpuinfo.jsonc",
"/home/anon/.config/waybar/modules/header.jsonc",
"/home/anon/.config/waybar/modules/idle_inhibitor.jsonc",
"/home/anon/.config/waybar/modules/keybindhint.jsonc",
"/home/anon/.config/waybar/modules/language.jsonc",
"/home/anon/.config/waybar/modules/memory.jsonc",
"/home/anon/.config/waybar/modules/mpris.jsonc",
"/home/anon/.config/waybar/modules/network.jsonc",
"/home/anon/.config/waybar/modules/notifications.jsonc",
"/home/anon/.config/waybar/modules/power.jsonc",
"/home/anon/.config/waybar/modules/privacy.jsonc",
"/home/anon/.config/waybar/modules/pulseaudio.jsonc",
"/home/anon/.config/waybar/modules/spotify.jsonc",
"/home/anon/.config/waybar/modules/taskbar.jsonc",
"/home/anon/.config/waybar/modules/theme.jsonc",
"/home/anon/.config/waybar/modules/tray.jsonc",
"/home/anon/.config/waybar/modules/updates.jsonc",
"/home/anon/.config/waybar/modules/wallchange.jsonc",
"/home/anon/.config/waybar/modules/window.jsonc",
"/home/anon/.config/waybar/modules/workspaces.jsonc",
"/home/anon/.config/waybar/modules/battery.jsonc",
"/home/anon/.local/share/waybar/modules/image#wallpaper.json",
"/home/anon/.local/share/waybar/modules/privacy.json",
"/home/anon/.local/share/waybar/modules/tray.json",
"/home/anon/.local/share/waybar/modules/wlr-taskbar#windows.json",
"/home/anon/.local/share/waybar/modules/wlr-taskbar.json",
"/home/anon/.local/share/waybar/modules/backlight.jsonc",
"/home/anon/.local/share/waybar/modules/battery.jsonc",
"/home/anon/.local/share/waybar/modules/bluetooth.jsonc",
"/home/anon/.local/share/waybar/modules/cava.jsonc",
"/home/anon/.local/share/waybar/modules/clock.jsonc",
"/home/anon/.local/share/waybar/modules/cpu.jsonc",
"/home/anon/.local/share/waybar/modules/custom-app-launcher.jsonc",
"/home/anon/.local/share/waybar/modules/custom-cava.jsonc",
"/home/anon/.local/share/waybar/modules/custom-clipboard.jsonc",
"/home/anon/.local/share/waybar/modules/custom-cliphist.jsonc",
"/home/anon/.local/share/waybar/modules/custom-cpuinfo.jsonc",
"/home/anon/.local/share/waybar/modules/custom-display.jsonc",
"/home/anon/.local/share/waybar/modules/custom-dunst.jsonc",
"/home/anon/.local/share/waybar/modules/custom-gamemode.jsonc",
"/home/anon/.local/share/waybar/modules/custom-github_hyde.jsonc",
"/home/anon/.local/share/waybar/modules/custom-gpuinfo#amd.jsonc",
"/home/anon/.local/share/waybar/modules/custom-gpuinfo#intel.jsonc",
"/home/anon/.local/share/waybar/modules/custom-gpuinfo#nvidia.jsonc",
"/home/anon/.local/share/waybar/modules/custom-gpuinfo.jsonc",
"/home/anon/.local/share/waybar/modules/custom-hyde-menu.jsonc",
"/home/anon/.local/share/waybar/modules/custom-hyprsunset.jsonc",
"/home/anon/.local/share/waybar/modules/custom-keybindhint.jsonc",
"/home/anon/.local/share/waybar/modules/custom-mediaplayer.jsonc",
"/home/anon/.local/share/waybar/modules/custom-power.jsonc",
"/home/anon/.local/share/waybar/modules/custom-powermenu.jsonc",
"/home/anon/.local/share/waybar/modules/custom-sensorsinfo.jsonc",
"/home/anon/.local/share/waybar/modules/custom-spotify.jsonc",
"/home/anon/.local/share/waybar/modules/custom-swaync.jsonc",
"/home/anon/.local/share/waybar/modules/custom-theme.jsonc",
"/home/anon/.local/share/waybar/modules/custom-updates.jsonc",
"/home/anon/.local/share/waybar/modules/custom-wallchange.jsonc",
"/home/anon/.local/share/waybar/modules/custom-wbar.jsonc",
"/home/anon/.local/share/waybar/modules/custom-weather.jsonc",
"/home/anon/.local/share/waybar/modules/custom-workflows.jsonc",
"/home/anon/.local/share/waybar/modules/gamemode.jsonc",
"/home/anon/.local/share/waybar/modules/group-eyecare.jsonc",
"/home/anon/.local/share/waybar/modules/group-hide-tray.jsonc",
"/home/anon/.local/share/waybar/modules/group-mediaplayer.jsonc",
"/home/anon/.local/share/waybar/modules/group-volumecontrol.jsonc",
"/home/anon/.local/share/waybar/modules/hyprland-language.jsonc",
"/home/anon/.local/share/waybar/modules/hyprland-window.jsonc",
"/home/anon/.local/share/waybar/modules/hyprland-workspaces#kanji.jsonc",
"/home/anon/.local/share/waybar/modules/hyprland-workspaces#roman.jsonc",
"/home/anon/.local/share/waybar/modules/hyprland-workspaces.jsonc",
"/home/anon/.local/share/waybar/modules/idle_inhibitor.jsonc",
"/home/anon/.local/share/waybar/modules/image#profile.jsonc",
"/home/anon/.local/share/waybar/modules/image#wallpaper.jsonc",
"/home/anon/.local/share/waybar/modules/memory.jsonc",
"/home/anon/.local/share/waybar/modules/mpd.jsonc",
"/home/anon/.local/share/waybar/modules/mpris.jsonc",
"/home/anon/.local/share/waybar/modules/network#bandwidth.jsonc",
"/home/anon/.local/share/waybar/modules/network.jsonc",
"/home/anon/.local/share/waybar/modules/power-profiles-daemon.jsonc",
"/home/anon/.local/share/waybar/modules/privacy.jsonc",
"/home/anon/.local/share/waybar/modules/pulseaudio#microphone.jsonc",
"/home/anon/.local/share/waybar/modules/pulseaudio.jsonc",
"/home/anon/.local/share/waybar/modules/temperature.jsonc",
"/home/anon/.local/share/waybar/modules/tray.jsonc",
"/home/anon/.local/share/waybar/modules/wlr-taskbar#windows.jsonc",
"/home/anon/.local/share/waybar/modules/wlr-taskbar.jsonc"
],
"image#wallpaper": {
"size": 30,
"icon-size-multiplier": 3
},
"privacy": {
"icon-size": 10,
"icon-size-multiplier": 1
},
"tray": {
"icon-size": 16,
"icon-size-multiplier": 1.6
},
"wlr/taskbar#windows": {
"icon-size": 16,
"icon-size-multiplier": 1.6
},
"wlr/taskbar": {
"icon-size": 16,
"icon-size-multiplier": 1.6
},
"position": "top"
}
+10 -5
View File
@@ -7,7 +7,10 @@
min-height: 10px;
}
@import "../theme.css"; /* HyDE/wallbash-generated colors, lives one level up */
@import "../theme.css";
/* HyDE/wallbash-generated colors, one level up. FATAL if missing
waybar exits before drawing anything. install.sh seeds it from ../theme-fallback.css on
hosts where wallbash never wrote one. */
window#waybar {
/* transparent so the cavabar (bottom layer, same region) shows through; module pills keep @main-bg */
@@ -70,13 +73,13 @@ tooltip {
padding-left: 3px;
padding-right: 3px;
margin-right: 0px;
color: @wb-color;
color: @main-fg;
animation: tb_normal 20s ease-in-out 1;
}
#taskbar button.active {
background: @wb-act-bg;
color: @wb-act-color;
color: @wb-act-fg;
/*animation: tb_active 20s ease-in-out 1;*/
box-shadow: 0 0 1px 1px rgba(128, 128, 128, 0.4);
transition: all 0.4s cubic-bezier(0.55, -0.68, 0.48, 1.682);
@@ -84,7 +87,7 @@ tooltip {
#taskbar button:hover {
background: @wb-hvr-bg;
color: @wb-hvr-color;
color: @wb-hvr-fg;
opacity: 0.8;
animation: tb_hover 20s ease-in-out 1;
transition: all 0.3s cubic-bezier(0.55, -0.68, 0.48, 1.682);
@@ -132,7 +135,9 @@ tooltip {
#custom-rl_end,
#custom-rr_end {
color: @main-fg;
background: @main-bg;
/* background: @main-bg; */
background: transparent;
opacity: 1;
margin: 4px 0px 4px 0px;
padding-left: 4px;
+73 -11
View File
@@ -17,12 +17,15 @@
"modules-left": ["custom/l_end","custom/power","custom/r_end","hyprland/workspaces", "custom/sl_end","hyprland/window","custom/sr_end","custom/l_end","custom/wallchange","custom/cliphist","custom/r_end"],
"modules-center": ["wlr/taskbar"],
"modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"],
"modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","network","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"],
// sourced from modules based on config.ctl //
"tray": {
"icon-size": 19,
// exactly 22: Tela only has fixed-size monochrome icons in {16,22,24}/panel any
// other size makes GTK fall through to scalable/devices, whose icons are colored
// circles (the blue nm-applet blob). An exact panel match keeps the tray flat/white.
"icon-size": 22,
"rotate": 0,
"spacing": 5
},
@@ -36,7 +39,7 @@
"max-length": 1000,
"rewrite": {
"(.*) — Kitty": " $1",
"(.*)~": " $1",
"(.*)~": " $1",
"(.*) — Mozilla Firefox": "󰈹 $1",
"(.*)Mozilla Firefox": "󰈹 Firefox",
"(.*) — LibreWolf": "󰈹 $1",
@@ -60,7 +63,34 @@
"(.*)Gajim": "󰭹 Gajim",
"(.*)KeePassXC": " KeePassXC",
"(.*)Betterbird": "󰇮 Betterbird",
"(.*)Spotify": "󰓇 Spotify"
// --- apps in use on this rig (glyphs verified against JetBrainsMono NF v3) ---
"nvim (.*)": " $1",
"nvim": " Nvim",
"(.*)PyRadio - Playing: (.*)": "󰐹 $2",
"(.*)PyRadio(.*)": "󰐹 PyRadio",
"(.*) - mpv": "󰿎 $1",
"(.*)mpv": "󰿎 mpv",
"(.*) — Zen Browser": "󰖟 $1",
"(.*)Zen Browser(.*)": "󰖟 Zen",
"(.*) - Thorium": " $1",
"(.*)Thorium(.*)": " Thorium",
"(.*)Ghostty(.*)": "󰊠 Ghostty",
"(.*)zellij(.*)": "󰆍 zellij",
"(.*)btop(.*)": "󰨇 btop",
"(.*)lazydocker(.*)": "󰡨 lazydocker",
"(.*)Dolphin(.*)": "󰉋 Dolphin",
"(.*)qBittorrent(.*)": "󰇚 qBittorrent",
"(.*)GIMP(.*)": "󱇣 GIMP",
"(.*)LibreOffice(.*)": "󰈙 LibreOffice",
"(.*)KiCad(.*)": "󰘚 KiCad",
"(.*)QGroundControl(.*)": "󰐴 QGroundControl",
"(.*)Foxglove(.*)": "󰚩 Foxglove",
"(.*)Bruno(.*)": "󱂛 Bruno",
"(.*)RustDesk(.*)": "󰢹 RustDesk",
"(.*)scrcpy(.*)": "󰄜 scrcpy",
"(.*)Vial(.*)": "󰌌 Vial",
"(.*)Volume Control(.*)": "󰕾 Volume Control",
"(.*)Satty(.*)": "󰹑 Satty"
}
},
"hyprland/workspaces": {
@@ -181,12 +211,14 @@
},
"custom/wallchange": {
"format": "{}",
"format": "󰸉{}",
"rotate": 0,
"exec": "echo ; echo 󰆊 switch wallpaper",
"on-click": "swwwallpaper.sh -n",
"on-click-right": "swwwallpaper.sh -p",
"on-click-middle": "sleep 0.1 && swwwallselect.sh",
// HyDE 26.x removed swwwallpaper.sh/swwwallselect.sh; hyde-shell wallpaper is the
// replacement (same calls as upstream's custom-wallchange module)
"on-click": "hyde-shell wallpaper -n",
"on-click-right": "hyde-shell wallpaper -p",
"on-click-middle": "sleep 0.1 && hyde-shell wallpaper --select",
"interval" : 86400, // once every day
"tooltip": true
},
@@ -194,7 +226,7 @@
"pulseaudio": {
"format": "{icon} {volume}",
"rotate": 0,
"format-muted": "",
"format-muted": "󰝟",
"on-click": "pavucontrol -t 3",
"on-click-right": "volumecontrol.sh -s ''",
"on-click-middle": "volumecontrol.sh -o m",
@@ -216,7 +248,7 @@
"pulseaudio#microphone": {
"format": "{format_source}",
"rotate": 0,
"format-source": "",
"format-source": "",
"format-source-muted": "",
"on-click": "pavucontrol -t 4",
"on-click-middle": "volumecontrol.sh -i m",
@@ -232,7 +264,10 @@
"format-paused": "{status_icon} <i>{dynamic}</i>",
"player-icons": {
"default": "▶",
"mpv": "🎵"
"mpv": "󰝚",
"spotify": "󰓇",
"firefox": "󰈹",
"vlc": "󰕼"
},
"status-icons": {
"paused": ""
@@ -242,6 +277,16 @@
},
"battery": {
// Pinned to the laptop battery on purpose. Unpinned, waybar 0.15 scans every
// /sys/class/power_supply node, and a flapping one (the DualShock's
// ps-controller-battery-* appearing at login while opensdd/scc-daemon wake it)
// makes refreshBatteries() throw off its worker thread SIGABRT, killing the
// whole bar (battery.cpp:142 uncaught; all 5 of 2026-08-17's coredumps). The pin
// (battery.cpp:117) gates every read/watch of non-matching nodes. On wm no BAT0
// exists so the module stays hidden same as before. On lw verify the name with
// `ls /sys/class/power_supply` and correct here if it's BAT1 (its old default-config
// log already ruled out BAT2); a wrong name only hides the module, never crashes.
"bat": "BAT0",
"states": {
"good": 95,
"warning": 30,
@@ -255,6 +300,23 @@
"format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]
},
// added 2026-08-14 the rig never had a network indicator ("ethernet icon not working").
// Same def as HyDE's ~/.local/share/waybar/modules/network.jsonc, inline like everything
// else here; click toggles a bandwidth readout, tooltip carries the connection details.
"network": {
"tooltip": true,
"format-wifi": "<span size='150%'> </span>",
"rotate": 0,
"format-ethernet": "<span size='150%'>󰈀 </span>",
"tooltip-format": "Network: <big><b>{essid}</b></big>\nSignal strength: <b>{signaldBm}dBm ({signalStrength}%)</b>\nFrequency: <b>{frequency}MHz</b>\nInterface: <b>{ifname}</b>\nIP: <b>{ipaddr}/{cidr}</b>\nGateway: <b>{gwaddr}</b>\nNetmask: <b>{netmask}</b>",
"format-linked": "󰈀 {ifname} (No IP)",
"format-disconnected": "<span size='150%'>󰖪 </span>",
"tooltip-format-disconnected": "Disconnected",
"format-alt": "<span foreground='#99ffdd'> {bandwidthDownBytes}</span> <span foreground='#ffcc66'> {bandwidthUpBytes}</span>",
"on-click-right": "nm-connection-editor", // replaces the hidden nm-applet tray menu
"interval": 2
},
"backlight": {
"device": "intel_backlight",
"rotate": 0,
@@ -1,10 +0,0 @@
"backlight": {
"device": "intel_backlight",
"rotate": ${r_deg},
"format": "{icon} {percent}%",
"format-icons": ["", "", "", "", "", "", "", "", ""],
"on-scroll-up": "brightnessctl set 1%+",
"on-scroll-down": "brightnessctl set 1%-",
"min-length": 6
},
-14
View File
@@ -1,14 +0,0 @@
"battery": {
"states": {
"good": 95,
"warning": 30,
"critical": 20
},
"format": "{icon} {capacity}%",
"rotate": ${r_deg},
"format-charging": " {capacity}%",
"format-plugged": " {capacity}%",
"format-alt": "{time} {icon}",
"format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]
},
@@ -1,15 +0,0 @@
"bluetooth": {
"format": "",
"rotate": ${r_deg},
"format-disabled": "",
"format-connected": " {num_connections}",
"format-connected-battery": "{icon} {num_connections}",
// "format-connected-battery": "{icon} {device_alias}-{device_battery_percentage}%",
"format-icons": ["󰥇", "󰤾", "󰤿", "󰥀", "󰥁", "󰥂", "󰥃", "󰥄", "󰥅", "󰥆", "󰥈"],
// "format-device-preference": [ "device1", "device2" ], // preference list deciding the displayed device If this config option is not defined or none of the devices in the list are connected, it will fall back to showing the last connected device.
"tooltip-format": "{controller_alias}\n{num_connections} connected",
"tooltip-format-connected": "{controller_alias}\n{num_connections} connected\n\n{device_enumerate}",
"tooltip-format-enumerate-connected": "{device_alias}",
"tooltip-format-enumerate-connected-battery": "{device_alias}\t{icon} {device_battery_percentage}%"
},
-24
View File
@@ -1,24 +0,0 @@
{
"cava": {
// "cava_config": "$XDG_CONFIG_HOME/cava/cava.conf",
"framerate": 30,
"autosens": 1,
"sensitivity": 100,
"bars": 14,
"lower_cutoff_freq": 50,
"higher_cutoff_freq": 10000,
"method": "pulse",
"source": "auto",
"stereo": true,
"reverse": false,
"bar_delimiter": 0,
"monstercat": false,
"waves": false,
"noise_reduction": 0.77,
"input_delay": 2,
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
"actions": {
"on-click-right": "mode"
}
}
}
-11
View File
@@ -1,11 +0,0 @@
"custom/cliphist": {
"format": "{}",
"rotate": ${r_deg},
"exec": "echo ; echo 󰅇 clipboard history",
"on-click": "sleep 0.1 && cliphist.sh c",
"on-click-right": "sleep 0.1 && cliphist.sh d",
"on-click-middle": "sleep 0.1 && cliphist.sh w",
"interval" : 86400, // once every day
"tooltip": true
},
-25
View File
@@ -1,25 +0,0 @@
"clock": {
"format": "{:%H:%M}",
"rotate": ${r_deg},
"format-alt": "{:%R 󰃭 %d·%m·%y}",
"tooltip-format": "<span>{calendar}</span>",
"calendar": {
"mode": "month",
"mode-mon-col": 3,
"on-scroll": 1,
"on-click-right": "mode",
"format": {
"months": "<span color='#ffead3'><b>{}</b></span>",
"weekdays": "<span color='#ffcc66'><b>{}</b></span>",
"today": "<span color='#ff6699'><b>{}</b></span>"
}
},
"actions": {
"on-click-right": "mode",
"on-click-forward": "tz_up",
"on-click-backward": "tz_down",
"on-scroll-up": "shift_up",
"on-scroll-down": "shift_down"
}
},
-8
View File
@@ -1,8 +0,0 @@
"cpu": {
"interval": 10,
"format": "󰍛 {usage}%",
"rotate": ${r_deg},
"format-alt": "{icon0}{icon1}{icon2}{icon3}",
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
},
-10
View File
@@ -1,10 +0,0 @@
"custom/cpuinfo": {
"exec": " cpuinfo.sh",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000
},
-47
View File
@@ -1,47 +0,0 @@
// modules for padding //
"custom/l_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/r_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/sl_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/sr_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/rl_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/rr_end": {
"format": " ",
"interval" : "once",
"tooltip": false
},
"custom/padd": {
"format": " ",
"interval" : "once",
"tooltip": false
}
}
@@ -1,8 +0,0 @@
{
"custom/github_hyprdots": {
"format": " ",
"rotate": 0,
"tooltip-format": "  Hyprdots repository",
"on-click": "xdg-open https://github.com/prasanthrangan/hyprdots"
}
}
-41
View File
@@ -1,41 +0,0 @@
"custom/gpuinfo": {
"exec": " gpuinfo.sh",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000,
"on-click": "gpuinfo.sh --toggle",
},
"custom/gpuinfo#nvidia": {
"exec": " gpuinfo.sh --use nvidia ",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000,
},
"custom/gpuinfo#amd": {
"exec": " gpuinfo.sh --use amd ",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000,
},
"custom/gpuinfo#intel": {
"exec": " gpuinfo.sh --use intel ",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"interval": 5, // once every 5 seconds
"tooltip": true,
"max-length": 1000,
},
-13
View File
@@ -1,13 +0,0 @@
// --// waybar config generated by wbarconfgen.sh //-- //
{
// sourced from header module //
"layer": "top",
"position": "${w_position}",
"mod": "dock",
"${hv_pos}": ${w_height},
"exclusive": true,
"passthrough": false,
"gtk-layer-shell": true,
"reload_style_on_change": true,
@@ -1,9 +0,0 @@
"idle_inhibitor": {
"format": "{icon}",
"rotate": ${r_deg},
"format-icons": {
"activated": "󰅶",
"deactivated": "󰛊"
}
},
@@ -1,6 +0,0 @@
"custom/keybindhint": {
"format": " ",
"rotate": ${r_deg},
"on-click": "keybinds_hint.sh"
},
@@ -1,6 +0,0 @@
"hyprland/language": {
"format": "{short} {variant}",
"rotate": ${r_deg},
"on-click": "keyboardswitch.sh",
},
-18
View File
@@ -1,18 +0,0 @@
"memory": {
"states": {
"c": 90, // critical
"h": 60, // high
"m": 30, // medium
},
"interval": 30,
"format": "󰾆 {used}GB",
"rotate": ${r_deg},
"format-m": "󰾅 {used}GB",
"format-h": "󰓅 {used}GB",
"format-c": " {used}GB",
"format-alt": "󰾆 {percentage}%",
"max-length": 10,
"tooltip": true,
"tooltip-format": "󰾆 {percentage}%\n {used:0.1f}GB/{total:0.1f}GB"
},
-16
View File
@@ -1,16 +0,0 @@
"mpris": {
"format": "{player_icon} {dynamic}",
"rotate": ${r_deg},
"format-paused": "{status_icon} <i>{dynamic}</i>",
"player-icons": {
"default": "▶",
"mpv": "🎵"
},
"status-icons": {
"paused": ""
},
// "ignored-players": ["firefox"]
"max-length": 1000,
"interval": 1
},
-13
View File
@@ -1,13 +0,0 @@
"network": {
"tooltip": true,
"format-wifi": " ",
"rotate": ${r_deg},
"format-ethernet": "󰈀 ",
"tooltip-format": "Network: <big><b>{essid}</b></big>\nSignal strength: <b>{signaldBm}dBm ({signalStrength}%)</b>\nFrequency: <b>{frequency}MHz</b>\nInterface: <b>{ifname}</b>\nIP: <b>{ipaddr}/{cidr}</b>\nGateway: <b>{gwaddr}</b>\nNetmask: <b>{netmask}</b>",
"format-linked": "󰈀 {ifname} (No IP)",
"format-disconnected": "󰖪 ",
"tooltip-format-disconnected": "Disconnected",
"format-alt": "<span foreground='#99ffdd'> {bandwidthDownBytes}</span> <span foreground='#ffcc66'> {bandwidthUpBytes}</span>",
"interval": 2
}
@@ -1,29 +0,0 @@
"custom/notifications": {
"format": "{icon} {}",
"rotate": ${r_deg},
"format-icons": {
"email-notification": "<span foreground='white'><sup></sup></span>",
"chat-notification": "󱋊<span foreground='white'><sup></sup></span>",
"warning-notification": "󱨪<span foreground='yellow'><sup></sup></span>",
"error-notification": "󱨪<span foreground='red'><sup></sup></span>",
"network-notification": "󱂇<span foreground='white'><sup></sup></span>",
"battery-notification": "󰁺<span foreground='white'><sup></sup></span>",
"update-notification": "󰚰<span foreground='white'><sup></sup></span>",
"music-notification": "󰝚<span foreground='white'><sup></sup></span>",
"volume-notification": "󰕿<span foreground='white'><sup></sup></span>",
"notification": "<span foreground='white'><sup></sup></span>",
"dnd": "",
"none": ""
},
"return-type": "json",
"exec-if": "which dunstctl",
"exec": "notifications.py",
"on-scroll-down": "sleep 0.1 && dunstctl history-pop",
"on-click": "dunstctl set-paused toggle",
"on-click-middle": "dunstctl history-clear",
"on-click-right": "dunstctl close-all",
"interval": 1,
"tooltip": true,
"escape": true
},
-10
View File
@@ -1,10 +0,0 @@
"custom/power": {
"format": "{}",
"rotate": ${r_deg},
"exec": "echo ; echo  logout",
"on-click": "logoutlaunch.sh 2",
"on-click-right": "logoutlaunch.sh 1",
"interval" : 86400, // once every day
"tooltip": true
},
-19
View File
@@ -1,19 +0,0 @@
{
"privacy": {
"icon-size": 18,
"icon-spacing": 5,
"transition-duration": 250,
"modules": [
{
"type": "screenshare",
"tooltip": true,
"tooltip-icon-size": 24
},
{
"type": "audio-in",
"tooltip": true,
"tooltip-icon-size": 24
}
]
}
}
@@ -1,35 +0,0 @@
"pulseaudio": {
"format": "{icon} {volume}",
"rotate": ${r_deg},
"format-muted": "婢",
"on-click": "pavucontrol -t 3",
"on-click-right": "volumecontrol.sh -s ''",
"on-click-middle": "volumecontrol.sh -o m",
"on-scroll-up": "volumecontrol.sh -o i",
"on-scroll-down": "volumecontrol.sh -o d",
"tooltip-format": "{icon} {desc} // {volume}%",
"scroll-step": 5,
"format-icons": {
"headphone": "",
"hands-free": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", "", ""]
}
},
"pulseaudio#microphone": {
"format": "{format_source}",
"rotate": ${r_deg},
"format-source": "",
"format-source-muted": "",
"on-click": "pavucontrol -t 4",
"on-click-middle": "volumecontrol.sh -i m",
"on-scroll-up": "volumecontrol.sh -i i",
"on-scroll-down": "volumecontrol.sh -i d",
"tooltip-format": "{format_source} {source_desc} // {source_volume}%",
"scroll-step": 5
},
-15
View File
@@ -1,15 +0,0 @@
"custom/spotify": {
"exec": "mediaplayer.py --player spotify",
"format": " {}",
"rotate": ${r_deg},
"return-type": "json",
"on-click": "playerctl play-pause --player spotify",
"on-click-right": "playerctl next --player spotify",
"on-click-middle": "playerctl previous --player spotify",
"on-scroll-up": "volumecontrol.sh -p spotify i",
"on-scroll-down": "volumecontrol.sh -p spotify d",
"max-length": 25,
"escape": true,
"tooltip": true
},
-151
View File
@@ -1,151 +0,0 @@
* {
border: none;
border-radius: 0px;
font-family: "JetBrainsMono Nerd Font";
font-weight: bold;
font-size: ${s_fontpx}px;
min-height: 10px;
}
@import "theme.css";
window#waybar {
background: @bar-bg;
}
tooltip {
background: @main-bg;
color: @main-fg;
border-radius: ${t_radius}px;
border-width: 0px;
}
#workspaces button {
box-shadow: none;
text-shadow: none;
padding: 0px;
border-radius: ${w_radius}px;
margin-${x1}: ${w_margin}px;
margin-${x2}: ${w_margin}px;
margin-${x3}: 0px;
padding-${x3}: ${w_paddin}px;
padding-${x4}: ${w_paddin}px;
margin-${x4}: 0px;
color: @main-fg;
animation: ws_normal 20s ease-in-out 1;
}
#workspaces button.active {
background: @wb-act-bg;
color: @wb-act-fg;
margin-${x3}: ${w_margin}px;
padding-${x3}: ${w_padact}px;
padding-${x4}: ${w_padact}px;
margin-${x4}: ${w_margin}px;
animation: ws_active 20s ease-in-out 1;
transition: all 0.4s cubic-bezier(.55,-0.68,.48,1.682);
}
#workspaces button:hover {
background: @wb-hvr-bg;
color: @wb-hvr-fg;
animation: ws_hover 20s ease-in-out 1;
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
}
#taskbar button {
box-shadow: none;
text-shadow: none;
padding: 0px;
border-radius: ${w_radius}px;
margin-${x1}: ${w_margin}px;
margin-${x2}: ${w_margin}px;
margin-${x3}: 0px;
padding-${x3}: ${w_paddin}px;
padding-${x4}: ${w_paddin}px;
margin-${x4}: 0px;
color: @wb-color;
animation: tb_normal 20s ease-in-out 1;
}
#taskbar button.active {
background: @wb-act-bg;
color: @wb-act-color;
margin-${x3}: ${w_margin}px;
padding-${x3}: ${w_padact}px;
padding-${x4}: ${w_padact}px;
margin-${x4}: ${w_margin}px;
animation: tb_active 20s ease-in-out 1;
transition: all 0.4s cubic-bezier(.55,-0.68,.48,1.682);
}
#taskbar button:hover {
background: @wb-hvr-bg;
color: @wb-hvr-color;
animation: tb_hover 20s ease-in-out 1;
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
}
#tray menu * {
min-height: 16px
}
#tray menu separator {
min-height: 10px
}
${modules_ls}
#custom-l_end,
#custom-r_end,
#custom-sl_end,
#custom-sr_end,
#custom-rl_end,
#custom-rr_end {
color: @main-fg;
background: @main-bg;
opacity: 1;
margin: ${x1g_margin}px ${x2g_margin}px ${x3g_margin}px ${x4g_margin}px;
padding-${x3}: ${g_paddin}px;
padding-${x4}: ${g_paddin}px;
}
#workspaces,
#taskbar {
padding: 0px;
}
#custom-r_end {
border-radius: ${x1rb_radius}px ${x2rb_radius}px ${x3rb_radius}px ${x4rb_radius}px;
margin-${x4}: ${e_margin}px;
padding-${x4}: ${e_paddin}px;
}
#custom-l_end {
border-radius: ${x1lb_radius}px ${x2lb_radius}px ${x3lb_radius}px ${x4lb_radius}px;
margin-${x3}: ${e_margin}px;
padding-${x3}: ${e_paddin}px;
}
#custom-sr_end {
border-radius: 0px;
margin-${x4}: ${e_margin}px;
padding-${x4}: ${e_paddin}px;
}
#custom-sl_end {
border-radius: 0px;
margin-${x3}: ${e_margin}px;
padding-${x3}: ${e_paddin}px;
}
#custom-rr_end {
border-radius: ${x1rc_radius}px ${x2rc_radius}px ${x3rc_radius}px ${x4rc_radius}px;
margin-${x4}: ${e_margin}px;
padding-${x4}: ${e_paddin}px;
}
#custom-rl_end {
border-radius: ${x1lc_radius}px ${x2lc_radius}px ${x3lc_radius}px ${x4lc_radius}px;
margin-${x3}: ${e_margin}px;
padding-${x3}: ${e_paddin}px;
}
-17
View File
@@ -1,17 +0,0 @@
"wlr/taskbar": {
"format": "{icon}",
"rotate": ${r_deg},
"icon-size": ${i_task},
"icon-theme": "${i_theme}",
"spacing": 0,
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close",
"ignore-list": [
"Alacritty"
],
"app_ids-mapping": {
"firefoxdeveloperedition": "firefox-developer-edition"
}
},
-11
View File
@@ -1,11 +0,0 @@
"custom/theme": {
"format": "{}",
"rotate": ${r_deg},
"exec": "echo ; echo 󰟡 switch theme",
"on-click": "themeswitch.sh -n",
"on-click-right": "themeswitch.sh -p",
"on-click-middle": "sleep 0.1 && themeselect.sh",
"interval" : 86400, // once every day
"tooltip": true
},
-6
View File
@@ -1,6 +0,0 @@
"tray": {
"icon-size": ${i_size},
"rotate": ${r_deg},
"spacing": 5
},
-10
View File
@@ -1,10 +0,0 @@
"custom/updates": {
"exec": "systemupdate.sh",
"return-type": "json",
"format": "{}",
"rotate": ${r_deg},
"on-click": "hyprctl dispatch exec 'systemupdate.sh up'",
"interval": 86400, // once every day
"tooltip": true,
"signal": 20
},
@@ -1,11 +0,0 @@
"custom/wallchange": {
"format": "{}",
"rotate": ${r_deg},
"exec": "echo ; echo 󰆊 switch wallpaper",
"on-click": "swwwallpaper.sh -n",
"on-click-right": "swwwallpaper.sh -p",
"on-click-middle": "sleep 0.1 && swwwallselect.sh",
"interval" : 86400, // once every day
"tooltip": true
},

Some files were not shown because too many files have changed in this diff Show More