[Update] bulk update
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
--
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -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 } },
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user