dotfiles are your virtual home... this is my home
| My choice | |
|---|---|
| OS | CachyOS (Arch-based) + WSL2 on Windows 11 |
| Desktop | Wayland: sway + Noctalia on quark · niri on astrobit · none on adam (WSL2) |
| Shell | bash |
| Terminal | foot (Wayland) · Windows Terminal (WSL2) |
| Editor | vim |
| Theme | Solarized dark (everywhere) |
A dotfiles repository is not intended to be forked — it is a personal backup and a source of inspiration. Feel free to take what is useful; sharing configs is great.
- Dependencies
- Bootstrap
- Directory structure
- bash
- vim
- Plugin set
- Language Server Protocol (LSP)
- Linting and autofix (ALE)
- Completion workflow
- Git (fugitive + gitgutter)
- Fuzzy finders (fzf.vim)
- Navigation (easymotion)
- Text manipulation (surround, commentary, unimpaired, repeat)
- Alignment (tabular, easy-align, maketable)
- Incrementing columns (VisIncr)
- Auto-pairs (lexima)
- Buffer and file navigation (dirvish, bbye, tagbar)
- Folding (native markers)
- LaTeX (vimtex)
- Markdown (markdown-preview)
- Encryption (gnupg)
- Arithmetic (HowMuch)
- Per-filetype configuration
- Key mappings reference
- nvim
- git
- modules — HPC environments
- claude
- Layout
- Global configuration (CLAUDE.md, settings.json)
- Status line
- Slash commands
- Skills — three-class machinery (skills-apply)
- Claude CLI wrappers — overview
- Cloud: Anthropic
- Cloud: Z.ai
- Cloud: NVIDIA (build.nvidia.com)
- Local: three backends (ollama, llama.cpp, ik_llama.cpp)
- Local server management (llm-local-server)
- Ollama utilities
- llama.cpp utilities
- Machine-specific overrides
- Environment reference
- scripts
- desktop — quark's sway + Noctalia session
- desktop-astrobit — astrobit's niri session
- Extending the dotfiles
- Copyrights
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| git | Version control | pacman -S git |
apt install git |
| bash ≥ 4.4 | Shell (uses autocd, globstar, histappend) |
pre-installed | apt install bash |
| vim | Editor | pacman -S vim |
apt install vim |
| neovim ≥ 0.11 | Second editor, alongside vim | ~/.scripts/install-nvim (pacman) |
~/.scripts/install-nvim (upstream tarball; apt's 0.9 is too old) |
| GNU Stow | Symlink manager — deploys packages | pacman -S stow |
apt install stow |
| Lmod | Environment module system | yay -S lmod (AUR) |
apt install lmod |
| dircolors | Colour-coded ls output (reads ~/.dircolors.256dark) |
coreutils (pre-installed) | coreutils (pre-installed) |
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| fzf | Fuzzy finder — used by vim (fzf.vim) and shell |
pacman -S fzf |
apt install fzf |
| rsync | synccp / syncmv shell functions |
pacman -S rsync |
apt install rsync |
| bc | Arithmetic in shell functions | pacman -S bc |
apt install bc |
| lesspipe | Rich pager for binary files | pacman -S lesspipe |
apt install lesspipe |
| python3 | Claude Code status line, helper scripts, NVIDIA proxy venv (needs python3-venv on Debian/Ubuntu) |
pacman -S python |
apt install python3 python3-venv |
| curl | Ollama / proxy health checks in claude_code helpers |
pre-installed | apt install curl |
Not needed on adam (WSL2, no desktop session). Not used on astrobit either: that
machine runs niri, not sway, so it stows desktop-astrobit instead — niri
plus a standalone foot config, with no Noctalia templates enabled.
| Tool | Purpose | Arch / CachyOS |
|---|---|---|
| niri | Wayland compositor — astrobit only | pacman -S niri |
| Tool | Purpose | Arch / CachyOS |
|---|---|---|
| sway | Wayland compositor | pacman -S sway |
| noctalia | Desktop shell — bar, launcher, notifications, lock screen; renders the colour palette into other apps' configs | AUR / upstream |
| foot | Terminal ($term) |
pacman -S foot |
| adw-gtk-theme | GTK theme the Noctalia hook selects — note the package is adw-gtk-theme, not adw-gtk3, which does not exist in the repos |
pacman -S adw-gtk-theme |
| qt6ct | Qt6 platform theme — reads the generated palette. qt5ct is deliberately not installed: there is no qt5-base on this machine |
pacman -S qt6ct |
| btop | System monitor, themed via template | pacman -S btop |
| grim / slurp | Screenshots (Print, and region capture) |
pacman -S grim slurp |
These are not packaged in distro repos. Install to the paths expected by each
modulefile (see modules/.modules/<name>/<version>.lua for the exact root).
| Toolchain | Source |
|---|---|
| NVIDIA HPC SDK | developer.nvidia.com/hpc-sdk |
| Intel oneAPI Base + HPC Toolkit | intel.com/oneapi |
| AMD AOCC | developer.amd.com/amd-aocc |
| OpenMPI | open-mpi.org — build from source against the desired compiler |
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| Ollama | Local LLM server for claude-local |
yay -S ollama (AUR) |
curl -fsSL https://ollama.com/install.sh | sh |
| Claude Code | AI coding assistant (claude CLI) |
npm install -g @anthropic-ai/claude-code |
same |
| nvm | Node.js version manager | github.com/nvm-sh/nvm | same |
| pnpm | Fast Node package manager | npm install -g pnpm |
same |
| texlive + latexmk | LaTeX compilation | pacman -S texlive-core latexmk |
apt install texlive latexmk |
| ImageMagick | Image conversion scripts in scripts/.scripts/ |
pacman -S imagemagick |
apt install imagemagick |
| ffmpeg | Video-to-GIF and media helpers | pacman -S ffmpeg |
apt install ffmpeg |
| exiftool | EXIF metadata in image scripts | pacman -S perl-image-exiftool |
apt install libimage-exiftool-perl |
| borg | Automated backup (scripts/.scripts/borg-automated-backup.sh) |
pacman -S borg |
apt install borgbackup |
| nvidia-smi | GPU status in ollama-status |
part of NVIDIA driver | part of NVIDIA driver |
Deployment is handled by dotify.sh, a custom symlink script.
It creates ~/.bash/, ~/.vim/, etc. and populates them with symlinks
pointing back into this repository.
# 1. Clone the repository
git clone https://github.com/szaghi/dotfiles ~/dotfiles
# 2. (optional) copy your private bash file
cp your-private-stuff ~/dotfiles/bash/private
# 3. Run the deploy script
bash ~/dotfiles/dotify.shdotify.sh is idempotent — re-running it is safe; it overwrites symlinks with ln -fs.
None. bd (back-directory navigation) used to be a submodule and is now
vendored at scripts/.scripts/bd, so a plain git clone is complete — there
is nothing to initialise.
Every top-level directory is a stow package whose internal layout mirrors
$HOME, except machines/ (host profiles) and the two asset directories.
dotfiles/
├── bash/ shell configuration — common to every host
├── bash-adam/ adam's overrides (signing key, local LLM settings)
├── bash-astrobit/ astrobit's overrides
├── bash-quark/ quark's overrides
├── claude/ Claude Code configuration, skills and commands
├── desktop/ quark's Wayland desktop (sway + Noctalia + foot + Qt)
├── desktop-astrobit/ astrobit's Wayland desktop (niri + foot)
├── git/ git configuration and commit template
├── machines/ per-host package and skill lists — not a stow package
├── miscellanea/ single-file configs (latexmkrc, NAS mount script)
├── modules/ Lmod modulefiles for HPC toolchains
├── nvim/ Neovim configuration (lazy.nvim), independent of vim/
├── python/ Python env (pythonrc, pylintrc)
├── scripts/ scripts, user systemd units and standalone binaries
├── ssh/ ssh client configuration (config only, never keys)
├── usr/ desktop application entries (.desktop files)
├── vim/ vim configuration and plugins
└── dotify.sh deploy script
bash/.bashrc loads modular files from ~/.bash/:
| File | Purpose |
|---|---|
aliases |
Command shortcuts (ll, bd, pacman helpers, LaTeX, git push aliases) |
exports |
Environment variables |
paths |
PATH additions |
functions |
Shell utility functions |
compilers |
Compiler flags and module helpers |
optprogs |
Optional program configuration |
prompt |
Two-line bash prompt with git status integration |
claude_code |
Dual-mode Claude Code setup (local Ollama + cloud Anthropic) |
private |
Machine-specific secrets — not tracked |
The prompt is two-line and git-aware: it shows branch, dirty state, and ahead/behind counts.
Vim is the editor for everything I do — Fortran/HPC, Python, LaTeX, prose.
The setup is a single .vimrc plus per-filetype ftplugins and per-plugin
configuration files, all managed through vim-plug.
Persistent undo, native relative line numbers, LSP-driven navigation and
completion for Fortran/Python/LaTeX/bash, and ALE-powered linting round out
an IDE-grade experience that stays 100% Vim (no Neovim required).
- Leader:
,— all custom mappings are prefixed with,. - Localleader:
,— set beforeplug#beginso vimtex picks it up at load. - Persistent undo:
~/.vim/undo/(auto-created, excluded from stow/git). - Color scheme: Solarized dark with cursorline, 1-column foldcolumn,
signcolumn=yespinned (no gitgutter flicker).
Install / update plugins:
:PlugInstall " install missing plugins after pulling the repo
:PlugUpdate " update all plugins
:PlugClean! " remove plugins no longer listed in .vimrc| Category | Plugins |
|---|---|
| Appearance | vim-solarized8, lightline + lightline-bufferline, rainbow_parentheses |
| LSP & linting | yegappan/lsp, ALE |
| Git | vim-fugitive, vim-gitgutter |
| Fuzzy finders | fzf + fzf.vim |
| Navigation | easymotion, vim-dirvish, tagbar, vim-bbye |
| Text editing | vim-surround, vim-repeat, vim-commentary, vim-unimpaired, lexima, VisIncr |
| Alignment | tabular, vim-easy-align, vim-maketable |
| Languages | vimtex, markdown-preview.nvim, python-syntax |
| Utilities | vim-gnupg, HowMuch, plugconf |
Per-plugin config lives in vim/.vim/plugconf/*.vim (loaded by plugconf
after plug#end()). The file must be named after the plugin's directory
(vim-fugitive.vim, not fugitive.vim), otherwise it is silently skipped, and
plugconf only sees plugins on the runtimepath at plug#end(): on-demand
plugins ('for', 'on') are configured in .vimrc instead. mapleader is set
before plug#begin, so <leader> in plugconf mappings is ,.
Provided by yegappan/lsp, a pure-Vim9 LSP client (no Neovim, no Node). Configured servers:
| Language | Server | Install |
|---|---|---|
| Fortran | fortls | pipx install fortls |
| Python | basedpyright | pipx install basedpyright |
| LaTeX | texlab | GitHub release (Ubuntu 24.04 has no apt package) |
| Bash | bash-language-server | npm install -g bash-language-server |
One-shot install for all four plus shellcheck:
~/.scripts/install-vim-lsp.shThe script is idempotent and detects missing binaries, so it is safe to re-run after a distro upgrade or on a fresh machine.
Key mappings (active only in buffers with an attached server):
| Key | Action |
|---|---|
gd |
:LspGotoDefinition |
gr |
:LspShowReferences (populates quickfix) |
K |
:LspHover — docs in a floating popup under the cursor (dismissed on motion) |
<leader>lr |
:LspRename — rename symbol across project (not ,rn: it would make ,r wait) |
<leader>la |
:LspCodeAction — quick-fixes and refactors |
<leader>lf |
:LspFormat — format buffer via LSP |
[d / ]d |
Previous / next diagnostic |
<Tab> |
Trigger completion (see below) |
Fortran note. fortls diagnostics are intentionally disabled via
features: { diagnostics: v:false }. fortls's CLI --excl_paths only
accepts absolute paths, so on FoBiS-managed
repos with third_party/*/docs/api/src/ shadow trees it emits spurious
diagnostics for any symbol coming from a shadow-duplicated module.
Navigation, hover, completion and references still work perfectly —
only the red-E> diagnostic signs are suppressed. Projects that actually
want diagnostics can drop a per-project .fortls and flip the feature
back on.
ALE handles linting and fixing
without running its own LSP client (g:ale_disable_lsp = 1 — LSP ownership
stays with yegappan/lsp).
| Filetype | Linter | Fixer |
|---|---|---|
| Python | ruff | ruff + ruff_format on save |
| Bash / sh | shellcheck | — |
| Any | — | trim_whitespace, remove_trailing_lines |
- Python files are automatically formatted with ruff on
:w. Other filetypes only lint — no surprise autofix. - Diagnostic popup under the cursor:
:ALEDetail. - Gutter signs:
✗(error),!(warning).
Completion is manual, not auto-popup (to keep Vim feeling like Vim). The popup menu is triggered from LSP's omnifunc:
| Key (insert mode) | Behavior |
|---|---|
<Tab> |
If popup visible → next item; after a word char → trigger omnicomplete; otherwise → literal <Tab> |
<S-Tab> |
If popup visible → previous item; otherwise → literal <S-Tab> |
<CR> |
If popup visible → accept selected item; otherwise → delegate to lexima (auto-pair Enter) |
<C-x><C-o> |
Manual omnicomplete trigger |
The <Tab> behavior is context-aware: it will not eat indentation
whitespace at the start of a line.
vim-fugitive provides the git
porcelain inside Vim. vim-gitgutter
shows per-hunk change indicators in the signcolumn.
| Key | Action |
|---|---|
<leader>gs |
:Git — status window (stage/unstage with s/u, diff with =) |
<leader>gb |
:Git blame |
<leader>gd |
:Gdiffsplit — 3-way diff in a split |
<leader>gl |
:Git log --oneline --decorate --all |
<leader>gc |
:Git commit |
<leader>gp |
:Git push |
]c / [c |
Next / previous hunk (native ]c [c in diff mode) |
<leader>gh |
Preview hunk |
<leader>ga |
Stage hunk |
<leader>gu |
Undo (reset) hunk |
gitgutter's own <leader>h* maps are disabled (plugconf/vim-gitgutter.vim):
they made <leader>h (:History) wait timeoutlen. The keys match Neovim.
Example — stage a hunk, commit, push from inside Vim without leaving the buffer:
,gs " open status
→ press s on the file to stage
→ press cc to write the commit message
→ :wq to commit
,gp " push
signcolumn=yes is pinned in .vimrc so the gutter never flickers as
gitgutter adds or removes signs.
Requires fzf and ripgrep
on $PATH.
| Key | Action |
|---|---|
<leader>f |
:Files — fuzzy file finder in cwd |
<leader>b |
:Buffers — fuzzy buffer switcher |
<leader>r |
:Rg — ripgrep (type pattern, <CR> to run) |
<leader>t |
:Tags — ctags jump |
<leader>h |
:History — recently-opened files |
<leader>/ |
:Lines — fuzzy search across buffer lines |
easymotion gives 2-keystroke
jumps to anywhere visible. Default aggressive mappings are disabled
(g:EasyMotion_do_mapping = 0) so single-, mappings stay untouched.
| Key | Action |
|---|---|
,,s{char} |
Jump to any occurrence of {char} on screen |
,,w |
Jump to the start of a word |
,,j / ,,k |
Jump down / up to a target line |
,,l / ,,h |
Jump forward / backward within the current line |
Arrow keys for window splits:
| Key | Action |
|---|---|
<A-Up> / <A-Down> / <A-Left> / <A-Right> |
Focus window above / below / left / right |
<C-Right> / <C-Left> |
Next / previous buffer (bnext / bprevious) |
qq |
Close buffer (keeps window layout, via vim-bbye's :Bdelete) |
<C-N> |
Toggle relativenumber on current window |
<leader>v |
Toggle virtualedit=all (cursor allowed in empty columns) |
Four tpope plugins that become muscle memory within days.
vim-surround — manipulate surrounding delimiters:
| Command | Effect |
|---|---|
cs"' |
Change surrounding " to ': "foo" → 'foo' |
ds( |
Delete surrounding (): (foo) → foo |
ysiw] |
Wrap word in []: foo → [foo] |
yss) |
Wrap whole line in () |
S< (visual) |
Wrap selection in <> |
vim-commentary — toggle comments:
| Command | Effect |
|---|---|
gcc |
Toggle comment on current line |
gc{motion} |
Toggle comment over motion (e.g. gcap — around paragraph) |
gc (visual) |
Toggle comment on selection |
vim-unimpaired — paired bracket mappings:
| Command | Effect |
|---|---|
]q / [q |
Next / previous quickfix entry |
]l / [l |
Next / previous location list entry |
]b / [b |
Next / previous buffer |
]<Space> / [<Space> |
Add blank line below / above |
]e / [e |
Swap current line with next / previous |
yo{char} |
Toggle option: yos (spell), yow (wrap), yoh (hlsearch), yol (list), yon (number)… |
vim-repeat — lets . repeat
complex plugin actions (surround, commentary, unimpaired). No mappings;
just works.
Three overlapping plugins, used for different jobs.
vim-easy-align — interactive visual alignment (fastest for one-off work):
" select a visual block, then:
ga= " align on first =
ga*= " align on every =
ga<CR>, " align on , with centered spacing
tabular — scriptable alignment for macros and autocmds:
:Tabularize /=
:Tabularize /|vim-maketable — convert a CSV/TSV selection to a markdown/rst/org table. Useful when pasting data into prose.
VisIncr — fills a visual-block
column with an arithmetic / alphabetical / date / power sequence. More
expressive than Vim's native g<C-a> / g<C-x>.
" visually select a column, then:
:I " increment: 1 2 3 4 ...
:II " decrement
:IA " alphabetical: a b c d ...
:IYMD " dates: 2026-04-24 2026-04-25 ...
:IPOW2 " powers: 1 2 4 8 16 32 ...
Loaded lazily — vim-plug only sources it when one of the I* commands
is invoked.
lexima.vim auto-closes (), [],
{}, "", '' and friends. Smart enough to avoid duplicating the closer
when the cursor is already at it. <CR> inside an empty pair opens a new
indented block:
if foo| — press <CR> → if foo
| |
end
The completion config above wires lexima's <CR> expansion into the
completion-<CR> fallback, so both behaviors coexist.
vim-dirvish — directory browser.
Replaces netrw. :Dirvish or - (dash) opens the current file's directory
as a plain, sortable buffer.
- " open parent dir in dirvish
R " rename with native vim commands on the buffer
:x " filter (e.g. :sort)
Directory-as-buffer model — you :w to persist changes, like a normal file.
vim-bbye — :Bdelete closes a buffer
without destroying the window layout. Mapped to qq.
tagbar — ctags-driven symbol outline. Sidebar on the left, sorted:
| Key | Action |
|---|---|
<F3> |
Toggle tagbar |
Requires ctags (apt install exuberant-ctags or universal-ctags).
For Fortran/Python/LaTeX the LSP symbol lookup (gd, gr) is usually
more accurate; tagbar is the fallback for filetypes without an LSP.
.vimrc sets foldmethod=marker globally so {{{ / }}} triple-brace
markers define folds. Fold column shows as a 1-col gutter.
| Key | Action |
|---|---|
za |
Native: toggle fold under cursor |
zR / zM |
Native: open all / close all folds |
foldlevelstart=0 — files open fully folded. foldopen is tuned so
most navigation commands auto-open folds as needed.
A dedicated autocmd (augroup folding) temporarily sets foldmethod=manual
during insert mode to prevent fold recalculation on every keystroke in
large Fortran files.
vimtex is loaded for *.tex files.
Compilation uses latexmk (configured globally in ~/.latexmkrc, see
miscellanea/.latexmkrc). PDF viewer: Evince.
Key (localleader = ,) |
Action |
|---|---|
,ll |
Start continuous compilation (:VimtexCompile) |
,lv |
Forward search to PDF viewer (:VimtexView) |
,lc |
Clean aux files |
,le |
Show compilation errors in quickfix |
,li |
Show compilation info |
,lt |
Toggle table of contents |
The localleader = "," is set in .vimrc before plug#begin — setting
it later (e.g. in an ftplugin) is too late, vimtex registers its mappings
at plugin-load time.
markdown-preview.nvim
renders markdown live in the browser. Works in plain Vim despite the .nvim
suffix.
:MarkdownPreview " open preview in default browser
:MarkdownPreviewStop " stop the preview server
Dark theme (g:mkdp_theme = 'dark') and the preview stays open when leaving
the buffer (g:mkdp_auto_close = 0). Both are set in .vimrc, not plugconf:
the plugin loads on demand (markdown only), and plugconf only configures
plugins already on the runtimepath at plug#end(). All other tunables (TOC,
KaTeX math, Mermaid, PlantUML, custom CSS) keep the plugin defaults.
vim-gnupg transparently
encrypts/decrypts files matching *.gpg, *.pgp, *.asc. Opening a
.gpg file prompts for the passphrase; saving re-encrypts in place.
vim secrets.txt.gpg
" edit normally
:w
" → file is re-encrypted to disk
GPG agent handles passphrase caching.
HowMuch evaluates an arithmetic
expression in the current selection and appends the result. Loaded
lazily via :HowMuch.
" visually select: 2 * (3 + 4)
:HowMuch
" → appends: = 14
Per-filetype settings live in vim/.vim/ftplugin/<filetype>.vim and are
auto-sourced by Vim when a buffer of that type is opened:
| File | Purpose |
|---|---|
fortran.vim |
Per-buffer free/fixed source-form detection (b:fortran_fixed_source), textwidth per form, syntax folding |
python.vim |
4-space indent (overrides the global 3), indent-based folding, python-syntax highlight knobs |
tex.vim |
English spell check, synmaxcol=0 for long equations, vimtex viewer set to Evince |
A custom filetype detection file vim/.vim/ftdetect/fobos.vim treats
any file whose name starts with fobos as dosini (so
FoBiS project files get syntax
highlighting).
Common programming-file behavior (whitespace trimming, blank-line squash,
RainbowParentheses activation, syntax folding) is defined in a single
augroup programming block in .vimrc.
Leader = ,, Localleader = ,. Only custom mappings are listed — native
Vim keys still behave as usual.
Editing / windows
| Key | Mode | Action |
|---|---|---|
/ |
n, x | Search with Perl/Python regex semantics (\v) |
Y |
n | Yank to end of line (analogous to D) |
<NL> (Ctrl-J) |
n | Insert line break at cursor (opposite of J) |
v |
x | Cycle through visual → visual-block → visual-line |
<C-e> / <C-y> |
n | Scroll viewport 2 lines down / up |
<A-↑↓←→> |
n | Focus window above / below / left / right |
<F2> |
n | Toggle line wrap |
<C-N> |
n | Toggle relativenumber |
<leader>v |
n | Toggle virtualedit=all |
Buffers
| Key | Action |
|---|---|
<C-Right> / <C-Left> |
:bnext / :bprevious |
]b / [b |
Same (via unimpaired) |
qq |
:Bdelete (vim-bbye) |
Finders (fzf.vim) — see Fuzzy finders. LSP — see LSP. Git — see Git. Easymotion — see Navigation.
Plugin togglers
| Key | Action |
|---|---|
<F3> |
Toggle tagbar |
Neovim runs alongside vim, not instead of it. The nvim/ package is a
port of the vim setup to Lua and Neovim-native plugins, with the same leader
keys; it never reads a vim file.
The two editors share no mutable state:
| Resource | vim | Neovim |
|---|---|---|
| config | ~/.vimrc, ~/.vim/ |
~/.config/nvim/ (this package) |
| plugins | ~/.vim/plugged (vim-plug) |
~/.local/share/nvim/lazy (lazy.nvim) |
| treesitter parsers | — | ~/.local/share/nvim/site |
| undo | ~/.vim/undo |
~/.local/state/nvim/undo |
| history | ~/.viminfo |
~/.local/state/nvim/shada |
$EDITOR, git core.editor, difftool |
vim |
unchanged: still vim |
| LSP servers, fzf, rg, ruff, shellcheck | shared binaries | shared (stateless processes) |
Do not "reuse" the vim config with set rtp^=~/.vim | source ~/.vimrc
(:h nvim-from-vim): both editors would then run vim-plug on the same
~/.vim/plugged, yegappan/lsp is Vim9script and fails in Neovim, and Neovim's
undo files would land in vim's undo directory.
~/.scripts/install-nvim # nvim (pinned, sha256-checked) + rg, fd, tree-sitter CLI
bash ~/dotfiles/dotify.sh nvim # link ~/.config/nvim
nvim # bootstraps lazy.nvim, plugins and parsersOn Ubuntu the release unpacks into ~/.local/opt/nvim-v<version>/ and
~/.local/bin/nvim points at it; older versions stay on disk, so a rollback
is one ln -sfn. Upgrade by bumping NVIM_VERSION and NVIM_SHA256 in the
script. On Arch/CachyOS the script uses pacman.
Plugin revisions are pinned in lazy-lock.json (committed). :Lazy update
moves them forward; :Lazy restore returns to the lockfile.
nvim/.config/nvim/
├── init.lua requires config.options, keymaps, autocmds, lazy
├── lazy-lock.json plugin revisions
├── lua/config/ options, keymaps, autocmds, lazy.nvim bootstrap
├── lua/plugins/ one spec per plugin (family); extras.lua = on trial
├── ftplugin/fortran.lua fixed/free form, before the runtime reads it
├── after/ftplugin/ per-filetype options (fortran, python, tex)
└── queries/fortran/ rainbow-delimiters query (none upstream)
| vim | Neovim | Notes |
|---|---|---|
| vim-plug + plugconf | lazy.nvim | one spec file per plugin replaces plugconf |
| yegappan/lsp | native vim.lsp.config / vim.lsp.enable + nvim-lspconfig (data only) |
same servers, no mason |
| ALE | ruff server, bash-language-server, conform.nvim |
ruff fix + format on save for Python |
manual <Tab> omni-completion |
same <Tab> via vim.lsp.completion |
no auto-popup, as before |
| vim-solarized8 (24-bit) | maxmx03/solarized.nvim (24-bit) | neither depends on the terminal palette |
| lightline + bufferline | lualine | buffer tabline degrades full path → short path → filename |
| fzf.vim | fzf-lua | also serves vim.ui.select (code actions) |
| dirvish | oil.nvim | editable directory buffer; deletes go to trash |
| easymotion | flash.nvim | same ,, keys |
| surround / commentary / repeat | nvim-surround / built-in gc / not needed |
|
| unimpaired | built-in [b ]b [q ]q [<Space> |
|
| lexima | nvim-autopairs | |
| gitgutter | gitsigns | |
| tagbar | aerial.nvim | symbols from LSP, then treesitter |
| vim-bbye | snacks.bufdelete | |
| rainbow_parentheses | rainbow-delimiters | custom Fortran query |
| python-syntax, regex syntax | treesitter | not for LaTeX (vimtex keeps its syntax) |
| fugitive, vimtex, markdown-preview, gnupg, easy-align, VisIncr, HowMuch, maketable | kept | |
| tabular, FoldFocus | dropped | easy-align covers tabular |
New, on trial (lua/plugins/extras.lua): trouble.nvim, diffview.nvim,
todo-comments.nvim, render-markdown.nvim. Also which-key.nvim and
nvim-treesitter-context.
Leader = ,. Core editing keys (/, <C-e>, <A-arrows>, <F2>, <C-N>,
<leader>v, <C-J>, visual v, <C-Right>/<C-Left>, qq) are identical
to vim. Differences and additions:
| Key | Action | vim |
|---|---|---|
,f ,b ,r ,t ,h ,/ |
fzf-lua: files, buffers, live grep, tags, recent files, lines | fzf.vim |
gd gr K |
definition, references (quickfix), hover | same |
,lr |
LSP rename | same |
,la ,lf |
code action, format | same |
,ld |
line diagnostics | — |
[d ]d |
previous / next diagnostic, with float | same |
<Tab> / <CR> |
complete / accept (else autopairs) | same |
,,s ,,w ,,j ,,k ,,l ,,h |
flash jumps | same keys (easymotion) |
- |
oil: parent directory | dirvish |
,gs ,gb ,gd ,gl ,gc ,gp |
fugitive | same |
]c [c ,gh ,ga ,gu |
hunk next/prev, preview, stage, reset | same (gitgutter) |
,gD ,gH |
diffview: working tree, file history | — |
<F3> |
aerial outline | tagbar |
,xx ,xb ,xs ,xq |
trouble: diagnostics, buffer diagnostics, symbols, quickfix | — |
,T |
search TODO comments | — |
,m |
toggle in-buffer markdown rendering | — |
Both editors share this keymap. Rename is ,lr, not ,rn, in both: ,r is
grep, and a ,rn map would make ,r wait timeoutlen in every LSP buffer;
for the same reason hunk keys live under ,g, not gitgutter's default ,h*.
- Icons are off everywhere. The terminal font (Cascadia Mono) has no Nerd Font glyphs; every plugin is configured with ASCII or plain Unicode.
- WSL clipboard.
"+ycopies via OSC 52 (UTF-8 safe);"+ppastes via PowerShell with UTF-8 output forced.clipboardstays empty, as in vim. - Treesitter for Fortran parses the 189 first-party
~/fortran/adamfiles with 0 errors (cpp and OpenACC/OpenMP included). Folds come from treesitter wherever a parser exists;config/autocmds.luais the fallback. - fortls diagnostics are dropped, as in vim (spurious errors on FoBiS-fetched and FORD shadow trees).
v:errmsgmay readE31after opening markdown. It comes from Neovim's own markdown ftplugin undo (sil! nunmap), reproduces withnvim --clean, and never shows a message.
| File | Deployed to | Purpose |
|---|---|---|
git/.gitconfig |
~/.gitconfig |
Git identity, aliases, GPG signing |
git/.git-templates/git_commit_message_template |
~/.git-templates/git_commit_message_template |
Conventional Commits template |
git/.git-templates/hooks/post-commit |
~/.git-templates/hooks/post-commit |
Post-commit hook shipped with the template directory |
Commits follow Conventional Commits:
type(scope): description
commit.gpgsign = true is set repo-wide in git/.gitconfig; the key itself is
per machine, declared in bash-<machine>/.gitconfig.local (symlinked to
~/.gitconfig.local). adam, quark and astrobit therefore carry three different
keys — a key is tied to the machine that holds its secret half, not to the
identity. All three are uploaded to the same GitHub account; GitHub accepts any
number of GPG keys and verifies a commit against whichever one signed it.
Two pieces have to be present or signing fails:
-
The secret key. A fresh machine has an empty keyring, and
git commitfails withgpg: skipped "<id>": No secret key. Either import the existing key or generate a new one for that machine and update its.gitconfig.local:gpg --quick-generate-key "Stefano Zaghi <stefano.zaghi@gmail.com>" ed25519 sign never gpg --list-secret-keys --keyid-format=long # take the 16-hex id gpg --armor --export <id> # paste into GitHub → SSH and GPG keys
-
GPG_TTY. The agent needs to know which terminal to draw the pinentry prompt on; without it signing fails withInappropriate ioctl for device. Exported frombash/.bash/exports, with~/.gnupg/gpg-agent.confselectingpinentry-cursesso it also works over ssh and in a bare TTY.
The revocation certificate generated alongside the key
(~/.gnupg/openpgp-revocs.d/<fingerprint>.rev) is what revokes the key if it is
ever compromised. It is not in this repo and must not be — back it up somewhere
private and offline.
Lmod manages compiler toolchains.
Modulefiles live in modules/.modules/ (deployed to ~/.modules/ via stow).
Lmod is initialised automatically in bash/.bash/exports.
Daily usage:
module avail # list all available modules
module load gcc/15.1.0 # load GCC 15.1.0
module load nvhpc/24.11 # load NVIDIA HPC SDK 24.11
module load openmpi/5.0.7-gnu14.2.0 # load OpenMPI (built against GCC 14)
module list # show currently loaded modules
module unload gcc/15.1.0 # unload a single module
module purge # unload everythingThe bash prompt shows loaded modules automatically (env {gcc/15.1.0 openmpi/5.0.7-gnu14.2.0}).
Available modules:
| Module | Description |
|---|---|
gcc/15.1.0 |
GCC 15.1.0 compiler toolchain |
nvhpc/23.1 · 24.11 · 25 · 26 |
NVIDIA HPC SDK (nvfortran, nvc, nvc++) |
intel/oneapi |
Intel oneAPI compilers (ifort, icc, icx) |
amd/5.1.0 |
AMD AOCC 5.1.0 compilers |
openmpi/3.1.5-nvhpc{20.7,22.3,23.1} |
OpenMPI 3.1.5 built against NVHPC |
openmpi/4.1.4-gnu11.2.0 |
OpenMPI 4.1.4 built against GCC 11 |
openmpi/4.1.4-intel2021.5.0 |
OpenMPI 4.1.4 built against Intel 2021 |
openmpi/5.0.7-gnu14.2.0 |
OpenMPI 5.0.7 built against GCC 14 |
Adding a new module:
Modulefiles are Lua scripts. Create one at modules/.modules/<name>/<version>.lua
mirroring the existing ones. Minimal template:
whatis("Short one-line description")
help([[
Long description shown by `module help <name>/<version>`.
]])
local root = "/path/to/install"
family("compiler") -- optional: ensures only one compiler is active at a time
if not isDir(root) then
LmodError(root .. " not found")
end
prepend_path("PATH", pathJoin(root, "bin"))
prepend_path("LD_LIBRARY_PATH", pathJoin(root, "lib"))
prepend_path("MANPATH", pathJoin(root, "share/man"))
setenv("CC", "gcc")
setenv("CXX", "g++")
setenv("FC", "gfortran")Because ~/.modules is a stow symlink pointing into the repo, the new file is
immediately visible to Lmod — no re-stowing needed. Verify with module avail.
Configuration and wrappers for Claude Code, a CLI coding agent. The setup provides a single mental model over two cloud providers (Anthropic, Z.ai) and three local inference backends (Ollama, llama.cpp, ik_llama.cpp), plus project-aware context, persona instructions and a custom Solarized status line.
Everything is driven by one command — claude-help — which prints the
live quick-reference. If a detail here ever drifts from reality, trust
claude-help and open a PR against this section.
claude/ is a stow package deployed to ~/.claude/:
| File | Purpose |
|---|---|
CLAUDE.md |
Global persona and project rules (loaded into every session) |
settings.json |
Permissions, model default, status-line binding, enabled plugins, marketplaces |
settings.local.json |
Machine-specific overrides — gitignored |
statusline-command.sh |
Custom status line (Python-backed JSON parser, Solarized palette) |
commands/ |
Custom slash commands — /semantic-commit, /capture-findings (see Slash commands) |
skills/ |
Custom user-authored skills (class A) + manifest.toml for third-party loose skills (class C) — see Skills |
The shell wrappers live in a separate package: bash/.bash/claude_code
(≈820 LOC, sourced from ~/.bashrc). Machine-specific knobs (GPU IDs,
binary paths, model defaults) go into ~/.bash/claude_code.local —
loaded automatically at the end of claude_code if present.
CLAUDE.md is the system-prompt overlay, loaded into every Claude
session. It encodes:
- Persona — analytic peer, not service assistant; unvarnished honesty; challenge premises; steel-man before dismantling; Socratic questioning on evaluative disagreements.
- Repo layout rules —
~/fortran/for Fortran,~/python/for Python; verify paths before using them. - Build system rules — FoBiS.py is primary; no make/cmake substitutions;
dependency management via
FoBiS.py fetch, not submodules. - Fortran conventions —
.F90uppercase,implicit noneeverywhere,allocatableoverpointer, column-major loop ordering, kind specifications viaiso_fortran_env,iso_c_bindingfor C interop, strictimplicit SAVEtrap guard, OpenMPdefault(none). - GPU/OpenACC rules —
parallel loopwith explicitgang/vector,!$acc declarediscipline, atomic-ops red-flag, reproducibility caveats. - Python conventions — Ruff patterns (B904 chained exceptions, B905
strict=on zip, RUF002 no Unicode math symbols), PEP 604 union syntax, NumPy-style docstrings for scientific code, Makefile as standard dev interface,.venvalways,git-clifffor changelogs. - Commit rules — Conventional Commits; never
Co-authored-bylines for AI; never auto-commit. Claude cannot sign: its shell has no TTY, so the pinentry prompt cannot be drawn — commits are written by hand.
settings.json configures the runtime:
| Key | Value | Purpose |
|---|---|---|
permissions.allow |
Read/Write/Edit on ~/fortran/** and ~/python/** |
Frictionless tool use inside my project trees |
model |
opus |
Default cloud model |
statusLine.command |
bash /home/stefano/.claude/statusline-command.sh |
Custom status line |
enabledPlugins |
frontend-design, skill-creator, cli-anything, document-skills |
Pre-enabled plugins |
extraKnownMarketplaces |
anthropic-agent-skills, cli-anything |
Extra plugin sources |
alwaysThinkingEnabled |
false |
Thinking is opt-in per request |
effortLevel |
high |
Max reasoning effort by default |
skipDangerousModePermissionPrompt |
true |
Skip the --dangerously-skip-permissions warning |
agentPushNotifEnabled |
true |
Push notifications when background agents finish |
statusline-command.sh reads the Claude Code statusLine JSON from stdin,
parses it with a one-shot inline python3 call, and renders a Solarized-dark
status line:
<model>·<GPUs>×GPU │ <cwd-basename> │ <branch>[<dirty>↑n↓m] │ ctx:N% │ 5h:N%(Xh) │ 7d:N%(Xd)
- Model — from the JSON
display_name. On local backends, appended with<N>×GPUwhenCLAUDE_LOCAL_GPUSis exported. - Git — branch,
+staged,!unstaged,?untracked,$stashed,↑N↓Mahead/behind upstream. - Context % — percentage of context window used. Colour-graded green → yellow → orange → red at 50/80/95%.
- Rate limits — 5-hour and 7-day usage percentages with countdown to reset. Same colour ladder.
Requires python3 (used once per render) and works on any terminal with
256-colour support.
Custom slash commands are plain markdown files under claude/.claude/commands/,
deployed by stow as symlinks into ~/.claude/commands/. Lifecycle is identical
to class-A custom skills — git tracks the source, stow deploys symlinks, no
installer or extra machinery.
| Command | Purpose |
|---|---|
/semantic-commit |
Analyze staged diff + recent commit style, emit a Conventional Commits-formatted message (no auto-commit, no Co-authored-by lines) |
/capture-findings |
Capture session findings into repo docs, repo CLAUDE.md / .claude/, and global ~/.claude/CLAUDE.md — auto-detects branch, merge-base, and existing repo Claude config |
Each command is a markdown file with a YAML front-matter declaring its
description and the allowed-tools (whitelisted Bash invocations plus
file ops). The body is the prompt template; lines beginning with !`
are shell substitutions evaluated at invocation time (see
Claude Code custom commands docs).
See commands/semantic-commit.md for a minimal template and
commands/capture-findings.md for a richer one with git-aware substitutions.
Add a new slash command:
# 1. write the command (front-matter + prompt body)
vim ~/dotfiles/claude/.claude/commands/my-command.md
# 2. version it
cd ~/dotfiles && git add claude/.claude/commands/my-command.md
# 3. deploy (idempotent — stow only adds the new symlink)
bash dotify.sh claudeThe new /my-command is immediately available in the next Claude Code
session on this host; other hosts pick it up on their next git pull && bash dotify.sh claude.
Skills in ~/.claude/skills/ fall into three structurally different classes
with three different lifecycle owners. The dotfiles wire all three into one
declarative, per-host workflow via skills-apply (scripts/.scripts/skills-apply,
≈340 LOC bash) plus a TOML manifest. The full design is in
claude/.claude/skills/README.md; the
summary:
| Class | Examples | Lifecycle owner | Where declared |
|---|---|---|---|
| A. Custom user-authored | fobis, research-lookup, markdown-mermaid-writing, markitdown, scientific-writing |
git + stow | claude/.claude/skills/<name>/ (real source dirs) |
| B. Plugin / marketplace | frontend-design, skill-creator, cli-anything, document-skills |
claude plugin CLI |
settings.json → enabledPlugins |
| C. Third-party loose | pdf-deps, book-to-skill, stop-slop, … |
upstream installers (pipx, venv, curl | bash…) |
claude/.claude/skills/manifest.toml |
Per-host filtering of class C: machines/<hostname>.skills (one skill name
per line, blanks and # comments allowed). A missing file means "install
every manifest entry on this host". Class A always stows everywhere
(source-only, cheap); class B is host-uniform via settings.json.
skills-apply interface:
skills-apply install # install everything declared (idempotent)
skills-apply update # update everything installed
skills-apply status # show installed-vs-declared for all three classes
skills-apply remove <name> # uninstall one skill (plugin or manifest)Restart Claude Code after install or update so plugin changes load.
Adding a new skill:
- Class A — drop the directory under
claude/.claude/skills/<name>/(at minimum aSKILL.md),git add, re-stow withbash dotify.sh claude. - Class B —
claude plugin install <name>@<marketplace>, then commit the resultingsettings.jsonchange. Future hosts pick it up viaskills-apply install. - Class C — add a
[skills.<name>]block tomanifest.tomlwithcheck/install/update/uninstallshell commands; add the name to eachmachines/<host>.skillsfile that should sync it.
The new dotfiles dependency this adds is just claude (the Claude Code
CLI binary) — already required as a Development extra. Python 3 (already
required) is reused for inline tomllib parsing of the manifest.
All wrappers in bash/.bash/claude_code call the real claude binary
with a curated set of env vars and CLI flags. None replace the binary —
they're small convenience functions.
Overall flow:
┌────────────────────┐
│ claude-help │ full quick-reference
└────────────────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
CLOUD LOCAL UTILITIES
claude claude-local llm-local-server
claude-sonnet (ollama|llama|ikllama) ollama-{pull,create,…}
claude-opus llama-{models,alias,info}
claude-plan claude-nvidia nvidia-models
(proxy backend)
claude-zai[-fast|-turbo|-premium]
claude-local auto-starts the requested backend and auto-stops any
other local backend currently running — only one of ollama/llama/ikllama
is live at a time. llm-local-server is the lower-level management verb.
claude-nvidia is the odd one out: it reaches a cloud catalog but is
built on the local machinery, because NVIDIA's hosted API needs a
translation proxy running on this host. See
Cloud: NVIDIA.
Uses your standard Anthropic subscription (no extra key file).
| Command | What it does |
|---|---|
claude |
Subscription default (model from settings.json → opus) |
claude-sonnet |
Force Sonnet (claude --model sonnet) |
claude-opus |
Force Opus (claude --model opus) |
claude-plan |
Read-only plan mode (Opus) → auto-switches to Sonnet on execute (--permission-mode plan --model opusplan) |
Routes through Z.ai's Anthropic-compatible endpoint for the GLM model family.
| Command | Model |
|---|---|
claude-zai [model] |
default: glm-5-turbo, or any GLM model by ID |
claude-zai-fast |
glm-4.5-air |
claude-zai-turbo |
glm-5-turbo |
claude-zai-premium |
glm-5.1 |
- API key:
~/.z-ai-key(gitignored) orZAI_API_KEYenv override - Endpoint:
https://api.z.ai/api/anthropic
Routes through NVIDIA's hosted model catalog at build.nvidia.com.
Status: working, but not in practical use. The harness is complete and verified end-to-end; the blocker is on NVIDIA's side. Read Latency before spending time here.
claude-nvidia # default model
claude-nvidia deepseek-ai/deepseek-v4-flash-0731 # explicit model
claude-nvidia --no-autostop # leave the proxy running
nvidia-models # list the served catalog
nvidia-models deepseek # filter it- API key:
~/.nvidia-api-key(gitignored) orNVIDIA_API_KEYenv override - Upstream:
https://integrate.api.nvidia.com - Default model:
$NVIDIA_DEFAULT_MODEL(currentlydeepseek-ai/deepseek-v4-flash-0731)
Why this one needs a proxy. Z.ai is a two-env-var
wrapper because its endpoint is already Anthropic-compatible.
NVIDIA's hosted catalog is not — it serves only the OpenAI API, and
/v1/messages returns 404 there. Claude Code speaks the Anthropic
Messages API, so a local translation proxy
(nvd-claude-nim) sits in
between:
claude ──Anthropic /v1/messages──▶ 127.0.0.1:8787 ──OpenAI /v1/chat/completions──▶ integrate.api.nvidia.com
(local proxy)
That proxy is a server with a lifecycle, so it is managed as a fourth
llm-local-server backend rather than as a bare wrapper. claude-nvidia
auto-starts it exactly as claude-local auto-starts ollama:
llm-local-server start --backend nvidia
llm-local-server status --backend nvidia
llm-local-server stop --backend nvidiaTwo deliberate asymmetries with the local backends:
- It does not evict other backends.
claude-localstops any other local engine to free VRAM. NVIDIA inference is remote and the proxy is a few MB of Python, so it coexists with a running ollama — stopping a local model to reach a cloud one would be pure loss. --gpusis meaningless and ignored: no inference happens on this machine.
The proxy installs itself on first use into a dedicated venv at
~/.local/share/claude-nvidia-proxy (the WSL2 system Python is
externally managed). No manual setup beyond the key:
printf '%s' 'nvapi-...' > ~/.nvidia-api-key && chmod 600 ~/.nvidia-api-keyNote. NVIDIA's own Claude Code documentation describes self-hosted NIM containers, which do expose
/v1/messagesnatively on port 8000 and need no proxy at all. That is a different product from the hosted catalog an API key buys, and its instructions do not apply here.
The pipeline is verified working. A plain round-trip returns correctly translated Anthropic JSON:
{"content": [{"type": "thinking", ...}, {"type": "text", "text": "ROUNDTRIP OK"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 13, "output_tokens": 25}}What makes it unusable is latency variance on NVIDIA's side. Measured on 2026-09-15, same account, same model, requests minutes apart:
| Request | Result |
|---|---|
/v1/models (catalog) |
HTTP 200 in 0.19 s, every time |
| Short completion, no tools | a few seconds — then, unchanged, no response in 55 s |
Completion with tools |
no response in 190 s |
| Short completion, retried later | HTTP 200, nvcf-status: fulfilled |
The catalog endpoint always answers instantly, so this is neither network nor auth: it is queueing on NVIDIA's NVCF backend, where hosted catalog models run on shared capacity. An interactive coding agent issues many sequential requests, and a multi-minute stall on any one of them makes the session unworkable.
Therefore this backend is kept but not used. For day-to-day work
prefer claude-local — no shared queue, predictable latency. The reason
to keep the NVIDIA path is access to models too large for 2×12 GB, for
one-off non-interactive questions where a long wait is acceptable.
Not verified: tool calling. Every tool-enabled request fell into the latency window, so whether tool calls survive the Anthropic→OpenAI translation is untested. Claude Code is unusable as an agent without it, so assume nothing here until it is measured.
Other findings worth keeping:
deepseek-ai/deepseek-coder-6.7b-instructis listed in the catalog but returns404 Not found for account— listed does not mean enabled. Check a model id withnvidia-modelsand one real request before setting it as a default.- The proxy's liveness probe is
/healthz, not the/healththat llama.cpp uses. It is held in$NVIDIA_PROXY_HEALTH_PATHso the convention lives in one place; probing the wrong path makes a perfectly healthy proxy look like a 60-second startup timeout.
claude-local is a single entry point over three local inference servers.
It auto-starts the requested backend, stops any other local backend
currently running, and routes claude at the right port.
| Backend | Port | Why |
|---|---|---|
| ollama | 11434 | Easy model management (ollama pull), stable default |
| llama.cpp | 8080 | Direct GGUF loading, latest architectures first, fine-grained GPU offload |
| ik_llama.cpp | 8081 | Fork of llama.cpp with aggressive CPU/hybrid kernels and new quant types (IQ4_KS, IQ2_KS); faster for MoE models that spill to system RAM |
Usage:
# default backend (from LOCAL_DEFAULT_BACKEND, default: ollama)
claude-local
# explicit backend
claude-local --backend llama
claude-local --backend ikllama
# override model
claude-local --model qwen2.5-coder:14b
claude-local --backend llama --model /path/to/model.gguf
# override context (llama/ikllama only; Ollama requires ollama-create)
claude-local --backend llama --ctx 128k
# GPU topology (Ollama only; HPC-safe single-GPU mode)
claude-local --gpus 1
# do not auto-start / auto-stop
claude-local --no-autostart # server must already be running
claude-local --no-autostop # keep server alive after exit
# pass-through args to the claude binary
claude-local -- --verbose some promptThe -- [CLAUDE_ARGS] form separates wrapper flags from the real claude
invocation — anything after -- goes straight to claude.
llm-local-server is the lower-level control verb. claude-local calls
it implicitly, but use it directly when you want the server up or down
independent of Claude.
llm-local-server start # start default backend
llm-local-server start --backend llama --ctx 128k # start llama-server with custom ctx
llm-local-server stop # stop default backend
llm-local-server stop --backend llama # stop specific backend
llm-local-server start --backend nvidia # start the NVIDIA translation proxy
llm-local-server restart [opts] # stop + start
llm-local-server status # process + loaded model + GPU usagestatus shows the PID, the bound port, and nvidia-smi GPU memory/utilization.
With no --backend it reports all four backends.
The backend must be given as --backend <name>, never positionally:
llm-local-server start ollama is rejected with an explicit error rather
than being silently forwarded to the server process.
ollama-pull [model] # pull a model (wraps `ollama pull`)
ollama-create [--ctx N] [model] # create a context-capped variant (context window in Ollama is per-model)
ollama-models # list models grouped by type (coder, chat, embedding…)
ollama-rename <old> <new> # rename a model (preserves exact casing)ollama-create --ctx N is the workaround for Ollama's static context.
It clones a model with a PARAMETER num_ctx N override so you can pin a
specific context length per session.
llama-models # list all GGUFs in $LLAMACPP_MODELS_DIR + HF cache
llama-alias <name> <path> # create short-name alias (symlink) in models dir
llama-alias <name> # remove alias
llama-info [model] # dump GGUF metadata (arch, params, quant) + tensor summaryAliases let you invoke heavy GGUF paths via a short name:
llama-alias qwen3-32b ~/models/Qwen3-32B-Instruct-Q4_K_M.gguf
claude-local --backend llama --model qwen3-32bllama-info reads the GGUF header and prints architecture (llama/qwen/
mixtral/…), total parameters, quantization, tensor count, and context
window — useful when auditing a model before loading.
~/.bash/claude_code.local is loaded at the end of the main claude_code
file. Use it for anything that differs per machine:
# Example ~/.bash/claude_code.local
export LOCAL_DEFAULT_BACKEND="llama"
export LLAMACPP_BIN="/opt/llama.cpp/build/bin/llama-server"
export IKLLAMA_BIN="/opt/ik_llama.cpp/build/bin/llama-server"
export LLAMACPP_MODELS_DIR="$HOME/models"
export LLAMACPP_DEFAULT_MODEL="qwen3-coder-30b"
export LLAMACPP_CTX=262144
export LLAMACPP_GPU_LAYERS=-1
export CUDA_VISIBLE_DEVICES="0,1"
export NVIDIA_DEFAULT_MODEL="deepseek-ai/deepseek-v4-flash-0731"Deploy it via its own machine-specific stow package (e.g.
bash-adam/ listed in machines/adam) — see
Machine-specific packages.
| Variable | Default | Purpose |
|---|---|---|
LOCAL_DEFAULT_BACKEND |
ollama |
Backend used when claude-local runs without --backend |
OLLAMA_HOST |
127.0.0.1:11434 |
Ollama server bind |
OLLAMA_KEEP_ALIVE |
10m |
Keep model in VRAM after last request |
OLLAMA_NUM_PARALLEL |
1 |
Concurrent request slots |
OLLAMA_MAX_LOADED_MODELS |
1 |
Prevent VRAM thrashing |
OLLAMA_DEFAULT_MODEL |
qwen2.5-coder:7b |
Default model for claude-local --backend ollama |
LLAMACPP_BIN |
llama-server |
llama.cpp server binary |
LLAMACPP_HOST / LLAMACPP_PORT |
127.0.0.1 / 8080 |
Bind address |
LLAMACPP_MODELS_DIR |
~/models |
Where llama-models / llama-alias operate |
LLAMACPP_DEFAULT_MODEL |
(unset) | Default GGUF path or alias |
LLAMACPP_GPU_LAYERS |
-1 |
GPU offload layers (-1 = all) |
LLAMACPP_CTX |
131072 |
Default context window |
IKLLAMA_BIN |
llama-server |
ik_llama.cpp server binary |
IKLLAMA_HOST / IKLLAMA_PORT |
127.0.0.1 / 8081 |
Bind address (distinct from mainline) |
IKLLAMA_GPU_LAYERS |
99 |
GPU layers (ik_llama.cpp treats -1 as 0, so a large sentinel is used) |
IKLLAMA_CTX |
131072 |
Default context window |
IKLLAMA_DEFAULT_MODEL / IKLLAMA_MODELS_DIR |
fall back to LLAMACPP_* |
Resolved lazily at call time |
NVIDIA_API_KEY / ~/.nvidia-api-key |
(required) | NVIDIA auth (nvapi-…, from build.nvidia.com) |
NVIDIA_DEFAULT_MODEL |
deepseek-ai/deepseek-v4-flash-0731 |
Default model for claude-nvidia |
NVIDIA_PROXY_HOST / NVIDIA_PROXY_PORT |
127.0.0.1 / 8787 |
Local translation-proxy bind |
NVIDIA_PROXY_VENV |
~/.local/share/claude-nvidia-proxy |
Venv holding nvd-claude-nim (auto-created) |
NVIDIA_PROXY_HEALTH_PATH |
/healthz |
Proxy liveness probe (not /health, which llama.cpp uses) |
ZAI_API_KEY / ~/.z-ai-key |
(required) | Z.ai auth |
CLAUDE_LOCAL_GPUS |
(optional) | Shown in status line (<N>×GPU) when set by claude-local --gpus N |
Run claude-help at any time for the live version of this reference.
Everything lives flat in scripts/.scripts/ (deployed to ~/.scripts/), with
the sole exception of the Tecplot converters. The per-topic subdirectories this
table used to list disappeared in the migration to stow.
| Path | Purpose |
|---|---|
scripts/.scripts/bd |
Back-directory navigation — vendored, formerly a submodule |
scripts/.scripts/{convert,crop,scale,alpha,gray,overlap}_image |
Image processing utilities |
scripts/.scripts/{mountiso,mount_nas.sh} |
ISO and NAS mount helpers |
scripts/.scripts/{archive.sh,md-preview.sh,pps,rainix,rwd} |
Misc helpers (archive, preview, …) |
scripts/.scripts/borg-automated-backup.sh |
Borg backup automation |
scripts/.scripts/{pdf2grey,pdfA4scale,pdfcompress,image2pdf} |
PDF utilities |
scripts/.scripts/tecplot/ |
Tecplot format converters |
scripts/.scripts/{git-health,git-health-boot} |
Git integrity scanning — rationale and recovery playbook in scripts/.scripts/git-health.md |
scripts/.scripts/{noctalia-retheme,noctalia-qt-dim-disabled} |
Noctalia palette switching and Qt patch — quark |
scripts/.scripts/quark-desktop-install |
Root-owned desktop bits — quark |
scripts/.config/systemd/user/ |
systemd user units |
scripts/.bin/{act,hpc-login} |
Standalone binaries and HPC login helper |
scripts/.scripts/mbox-index.py |
Index Gmail Takeout MBOX into searchable SQLite (details) |
scripts/.scripts/skills-apply |
Declarative install/update/status for Claude Code skills across three classes (details) |
scripts/.scripts/claude-remote |
Claude Code sessions in tmux that survive SSH disconnects, reattachable from another host (details) |
skills-apply is the per-host driver for the three-class skills machinery
introduced in Skills. It
reads:
~/.claude/settings.json→enabledPlugins(class B — plugin/marketplace)~/.claude/skills/manifest.toml→[skills.*]blocks (class C — third-party)~/dotfiles/machines/<hostname>.skills(optional per-host class-C filter)
…and delegates to the upstream installer for each class: claude plugin install/update/uninstall for B, the manifest's own install/update/
uninstall shell strings for C. Class A (custom user-authored) is managed
entirely by stow — skills-apply status simply reports whether each
expected symlink resolves.
skills-apply install # idempotent: skip already-installed entries
skills-apply update # update every installed skill
skills-apply status # group by class, mark drift, exit 0
skills-apply remove <name> # uninstall (plugin or manifest); does NOT
# rewrite settings.json or manifest.toml —
# script warns you to do that yourselfRestart Claude Code after install or update so plugin changes load
(the claude plugin update CLI itself says "restart required to apply").
Dependencies: claude (Claude Code CLI), python3 (for inline tomllib
JSON/TOML parsing of settings.json and manifest.toml).
Reclaim Google account quota by exporting Gmail to a local archive that stays searchable after you delete the cloud copy. Indexes a Google Takeout MBOX export into a SQLite FTS5 database and extracts attachments to a date/sender file tree. Stdlib-only — no dependencies.
# 0. Get the mbox: Takeout (Mail → MBOX) → download zip → unzip
unzip -o takeout-*.zip -d /mnt/d/gmail-backup # yields Takeout/Mail/*.mbox
# 1. Build the searchable index + extract attachments
python3 ~/.scripts/mbox-index.py build /mnt/d/gmail-backup \
--db /mnt/d/gmail-backup/mail.db --attach-dir /mnt/d/gmail-backup/attachments
# 2. Search (full-text + filters: from: to: subject: has:attachment larger:N before:/after:)
python3 ~/.scripts/mbox-index.py search "invoice AND from:acme larger:5M after:2020-01-01"
python3 ~/.scripts/mbox-index.py search "larger:25M" # find quota hogs before deleting
# 3. Verify the archive, then delete from Gmail by size and empty Trash to reclaim quotaFull workflow — including the Takeout export, archive verification, and the cloud-deletion
steps — is in scripts/.scripts/mbox-index.md.
Start a Claude Code session on one host, walk away, and reattach later from another. Claude runs inside tmux on the host, so closing the laptop or losing the connection only detaches the view; the work goes on. The reference setup is adam as host (WSL2, at home, behind Vodafone's carrier-grade NAT) and quark as client (at the office), connected by Tailscale: both nodes dial out, so no public IP and no open port are needed.
| Role | Machine | Tailscale name | Needs |
|---|---|---|---|
| host | adam (WSL2) | wsl-adam |
tmux, claude, Tailscale (tailscale up --ssh --hostname=wsl-adam) |
| client | quark | quark |
ssh, Tailscale — no tmux |
# evening, on adam: start a session in the project, give Claude the task, detach (Ctrl-b d)
cd ~/fortran/foresight
claude-remote start
# next morning, on quark at the office: reattach to the same screen
claude-remote -H wsl-adam attach
# back on adam: reattach locally, or end it
claude-remote attach
claude-remote stopSessions take a name and a directory (claude-remote start harp ~/python/harp);
arguments after -- go to claude, and CLAUDE_REMOTE_CMD=claude-cfd-cnr launches one of
the wrappers instead. -H host runs any subcommand on
the host through ssh -t, forwarding those variables. Without the script on the client:
ssh -t stefano@wsl-adam tmux -u attach -d -t claude (-u forces UTF-8, -d detaches leftover clients — see below).
Leaving without killing the session — detach, never exit:
| Detach (Claude keeps running) | Ends the session |
|---|---|
Ctrl-b, release, then d (two keystrokes; Ctrl held on d does nothing) |
Ctrl-d or exit at the shell prompt in the session |
! tmux detach at Claude's prompt |
claude-remote stop |
claude-remote -H wsl-adam detach from another terminal |
|
| ssh escape Enter ~ ., or just close the terminal/laptop |
Leaving Claude itself (/exit) keeps the session: you land at its shell prompt, where
claude --continue resumes the conversation.
Things to know:
- A remote command gets only the system PATH.
~/.bashrcreturns at its PS1 guard before~/.bash/pathsruns, sossh wsl-adam claude-remote …fails with command not found. Use-H, which calls the remote copy by absolute path; the script also adds~/.scriptsand~/.local/binto its own PATH. - …and no locale. Without
LANG/LC_*tmux treats the client as non-UTF-8 and draws every non-ASCII character as_, which wrecks the alignment of Claude's TUI. The script forces UTF-8 (LANG=C.UTF-8,tmux -u). attachtakes the session over (tmux attach -d): a terminal left open on the other machine would otherwise stay attached as a second client.- The host must stay up. tmux outlives the SSH connection, not a sleeping or rebooting machine. On adam: Windows sleep set to never on AC, Windows Update paused, and the WSL VM kept alive (an open WSL terminal, or a keeper task at logon), or WSL shuts the distro down and takes the tmux server with it.
One-time setup (Tailscale on both nodes, keeper task), a full day's walkthrough, headless
runs and a troubleshooting table are in
scripts/.scripts/claude-remote.md.
Machine-specific (machines/quark); adam never stows it. quark runs CachyOS with
sway and the Noctalia desktop shell, which replaced waybar and supplies the bar,
launcher, notifications, OSDs and lock screen.
desktop/
├── .config/
│ ├── sway/config compositor: keybinds, input, borders
│ ├── foot/foot.ini terminal — includes the generated theme
│ ├── qt6ct/qt6ct.conf Qt6: Fusion style + custom palette
│ ├── chrome-flags.conf Chrome: native Wayland + GTK4
│ └── noctalia/patches/README.md why the local patches exist
├── system/ root-owned, not stowed; paths mirror /
│ ├── udev/hwdb.d/61-evdev-local.hwdb touchpad fuzz
│ ├── modprobe.d/iwlwifi-no-powersave.conf
│ └── NetworkManager/conf.d/wifi-powersave-off.conf
└── .stow-local-ignore keeps system/ out of the stow tree
Disabled here for the same reason as on astrobit — client-side power save hurts
Moonlight as much as the server side hurts Sunshine — but with different knobs.
quark's card is Intel (iwlwifi), where power_save is already off by default;
the culprit is iwlmvm.power_scheme, which defaults to 2 (balanced) and must be
1 (active). Do not add d0i3_disable: it does not exist in this kernel.
Dropping a file into NetworkManager/conf.d is not sufficient on its own. NM
consults that default only for profiles expressing no preference, and
nmcli device reapply reapplies IP settings rather than radio ones — so the
connection profile itself has to carry powersave=2, which is what
quark-desktop-install sets.
Noctalia renders the active palette into per-application config files ("templates"), which is what keeps foot, alacritty, GTK3/4, Qt6, btop and sway borders on one palette. The current scheme is Solarized dark.
Those generated files are gitignored — they are rebuilt on every palette change, and
a stow directory symlink would make Noctalia write into the repo. desktop/ therefore
tracks individual files, never a directory whose siblings Noctalia generates.
Enabling the template set is a GUI-only step (settings → Templates: gtk3, gtk4, qt, foot, alacritty, btop, sway); there is no supported settings.toml representation.
noctalia-retheme --list # schemes + current
noctalia-retheme builtin Nord # switch
noctalia-retheme community Solarized # switch backBuiltins: Catppuccin, Dracula, Gruvbox, Kanagawa, Nord, Oxocarbon. Afterwards: open a new
terminal, restart GTK/Qt apps, swaymsg reload.
The wrapper exists because color-scheme-set alone leaves the daemon rendering the old
palette (it needs an explicit config-reload), and templates-apply returns before the
files are written. It also re-applies a patch Noctalia would otherwise clobber:
noctalia-qt-dim-disabled (Qt disabled widgets are emitted identical to enabled ones).
Rationale and measurements: desktop/.config/noctalia/patches/README.md.
bash ~/dotfiles/dotify.sh desktop # symlinks the configs
~/.scripts/quark-desktop-install # touchpad hwdb + WiFi power save (root)Then enable the Noctalia templates in the GUI and run noctalia-retheme.
astrobit runs CachyOS with niri, a scrollable
tiling Wayland compositor, installed from the cachyos-niri-noctalia preset. It is
a separate package from desktop/ because almost nothing is shared with quark:
different compositor, and a foot config that does not read a generated theme.
| File | Deployed to | Purpose |
|---|---|---|
.config/niri/config.kdl |
~/.config/niri/config.kdl |
Nothing but include lines |
.config/niri/cfg/*.kdl |
~/.config/niri/cfg/ |
animation, autostart, display, input, keybinds, layout, misc, rules |
.config/foot/foot.ini |
~/.config/foot/foot.ini |
Terminal — Solarized inlined, not templated |
niri's own config is a single config.kdl; the split into cfg/*.kdl with
include lines comes from the preset and is kept because it makes a single
concern (keybinds, window rules) editable without scrolling through the rest.
Both levels are tracked, and stow links them file by file — the directories
in ~/.config/ stay real. That is deliberate: if the Noctalia templates are
ever enabled here, the daemon will write ~/.config/niri/noctalia next to the
symlinks instead of straight into this repository, which is the same rule
desktop/ follows for quark.
Editing backups (*.bak) left in ~/.config/niri/cfg/ are untracked on purpose.
astrobit is normally headless, reached through a web service; ssh covers most failures. Sunshine is the third fallback, for when the fault is in the GUI — a modal dialog blocking Ekos, a mount connection wizard. It therefore streams the live niri session: a dedicated RDP session would open a clean desktop next to the broken one, useless precisely when it is needed.
| File | Tracked | Why |
|---|---|---|
.config/sunshine/sunshine.conf |
yes | Hand-written; Sunshine does not rewrite it |
.config/sunshine/apps.json |
yes | The web UI does rewrite this — edits there show up as a diff |
sunshine_state.json, credentials/, *.log |
no | Web-UI password, TLS keypair, pairing state — per machine, never in git |
capture and encoder are deliberately not set: forcing capture = wlgrab
makes startup fail with "Could not initialize display with the given hw device
type". Autodetect picks wlgrab (niri does expose zwlr_screencopy_manager_v1,
despite being smithay rather than wlroots) and VAAPI on its own.
Resolution, frame rate and bitrate are client-side settings: fps,
resolutions and min_bitrate are not Sunshine options and are silently
ignored. Set them in Moonlight on quark — 1920x1080, 30 fps, ~15 Mbps.
desktop-astrobit/system/ is outside the stow tree (excluded by
.stow-local-ignore). Run sudo ~/.scripts/astrobit-system-install after
stowing to install the WiFi power-save settings and the virtual EDID. Idempotent.
Two things it configures deserve their reasons recorded:
- WiFi power save, on two levels. The RTL8821CE's
rtw88driver enables Deep Power Save by default, parking parts of the radio between beacons. On a link measured at -45 dBm with zero tx retries, that alone produced the Moonlight stutter and disconnections inherited from the Windows install. NetworkManager (wifi.powersave = 2) covers mac80211; Deep PS is reachable only as a module parameter, so it needs a reboot. quark needs the same fix with different knobs — Inteliwlwifi, wherepower_saveis already off butiwlmvm.power_schemedefaults to 2 (balanced) and must be 1 (active). - Virtual EDID. niri has no virtual outputs and no headless mode: unplug the monitor and it loses its only output. The blob advertises a permanent 1920x1080 on HDMI-A-2 — lower than the real 3440x1440 ultrawide, because this is what gets streamed. It replaces the real EDID always, so the physical monitor also runs at 1080p.
- Colours are inlined (Solarized dark,
[colors-dark]) rather than pulled in withinclude=~/.config/foot/themes/noctalia: no Noctalia template is enabled on this host, so there is no generated theme file to include. term=xterm-256colorinstead of the defaultterm=foot, because thefootterminfo entry does not exist on the remote HPC hosts and an unknownTERMbreaks curses applications over ssh.- Plain
monospaceat size 12: no Nerd Font is installed here, and the prompt degrades gracefully without the powerline glyphs.
If the config belongs to a tool already covered by a stow package, place the file at the correct mirrored path inside that package and re-run stow:
# Example: add a new bash helper
cp my-helper ~/dotfiles/bash/.bash/my-helper
# Re-deploy the package (safe to re-run, stow is idempotent)
bash ~/dotfiles/dotify.sh bashStow will create ~/.bash/my-helper → ~/dotfiles/bash/.bash/my-helper.
-
Create the package directory and mirror the
$HOMElayout inside it:# Example: track ~/.config/foo/config mkdir -p ~/dotfiles/foo/.config/foo cp ~/.config/foo/config ~/dotfiles/foo/.config/foo/config
-
Add the package name to
PACKAGESindotify.sh:PACKAGES=(bash vim git claude modules python scripts miscellanea foo)
-
Deploy:
bash ~/dotfiles/dotify.sh fooStow will symlink
~/.config/foo/config → ~/dotfiles/foo/.config/foo/config. -
Commit:
git add dotify.sh foo/ git commit
For configs that only belong on one machine, list the package in
machines/<hostname> (one package name per line):
echo "foo" >> ~/dotfiles/machines/$(hostname -s)dotify.sh reads this file automatically and stows those packages after the
common ones. The machines/ directory is tracked in git so the per-host
setup is reproducible.
My dotfiles come from many years of GNU/Linux usage and inspiration from countless people sharing their configs on the web. Distributed under the terms of the WTFPL — Do What the Fuck You Want to Public License, without any warranty.
Go to Top