Skip to content

Repository files navigation

confs

Personal dotfiles / config backups (macOS and Linux).

Install on a new machine

git clone https://github.com/mbottoni/confs.git ~/configs
cd ~/configs
./install.sh

That symlinks every config for the current OS into place. Anything it would overwrite is moved to ~/.dotfiles-backup/<timestamp>/ first (same relative path, so restoring is a mv away), and re-running is safe — links already pointing at the repo are left alone.

Preview first with ./install.sh --dry-run.

Options

Flag What it does
--dry-run, -n Print what would happen, write nothing
--copy Copy files instead of symlinking (edits then don't flow back to the repo)
--only zsh,tmux Install just those modules
--skip zotero Install everything except those
--list, -l List module names
--packages Also install the CLI tools the configs expect (brew / apt / dnf / pacman)
--cursor-extensions Also reinstall the Cursor extensions from cursor/extensions.txt
--help, -h Usage

Modules: zsh, shell, bash, zprofile (macOS), nvim, tmux, alacritty, lf, htop, mpv, yabai (macOS), claude, opencode, ollama, cursor, zotero. macOS-only modules are skipped automatically on Linux.

What it handles for you

  • Writes export ZDOTDIR="$HOME/.config/zsh" to ~/.zshenv.zshrc lives under ~/.config/zsh, and zsh won't find it otherwise.
  • Creates ~/.cache/zsh for HISTFILE.
  • Clones zsh-autosuggestions from upstream (the repo only has a placeholder).
  • Links ~/.tmux.conf~/.config/tmux/tmux.conf for tmux < 3.1.
  • On Linux, writes ~/.tmux_local.conf translating the pbcopy clipboard bindings to wl-copy / xclip / xsel (skipped if the file already exists).
  • Picks the right Cursor config directory per OS.

After installing

exec zsh                        # or restart the terminal
nvim +PlugInstall +qall         # fetch neovim plugins
chsh -s "$(command -v zsh)"     # if zsh is not the login shell yet
bash .macos                     # macOS defaults — optional, changes a lot

Zotero prefs are not applied automatically, see below.

Layout

Dir / file What it is Lives at
zsh/ zsh config, p10k, plugins ~/.config/zsh/ (needs ZDOTDIR)
.zprofile login shell profile ~/.zprofile
.bashrc bash config ~/.bashrc
aliasrc, shortcutrc shell aliases/shortcuts sourced from ~/.zshrc
nvim/ Neovim config (see below) ~/.config/nvim/
alacritty/ Alacritty terminal ~/.config/alacritty/
tmux/ tmux ~/.config/tmux/tmux.conf (+ ~/.tmux.conf link)
lf/, htop/, mpv/, yabai/ misc tools ~/.config/<tool>/
.macos macOS defaults script run once
vscode/ VS Code profile export import via VS Code profiles
cursor/ Cursor editor see below
claude/ Claude Code see below
opencode/ opencode (local-LLM agent) ~/.config/opencode/
llamacpp/ llama-server launch + agent see below
ollama/ Ollama model definition fallback, see below
zotero/ Zotero see below

Neovim

nvim/init.vim           -> ~/.config/nvim/init.vim
nvim/autoload/plug.vim  -> ~/.config/nvim/autoload/plug.vim   (vim-plug itself)

Plugins are not tracked — init.vim declares them and vim-plug fetches them into ~/.local/share/nvim/plugged/ on :PlugInstall. :PlugClean removes whatever is no longer declared.

Note there is also a stale ~/.config/nvim/plugged/ on older machines: that was the install path of an earlier vim-plug. The current one uses stdpath('data'), so anything under ~/.config/nvim/plugged is dead weight and can be deleted.

Completion is Copilot only — coc.nvim was removed, along with supertab, completion-nvim and deoplete-jedi (three things competing for <Tab>). Linting is ALE. vim-pydocstring needs doq, which its make install hook installs on first :PlugInstall.

Cursor

cursor/settings.json      -> ~/Library/Application Support/Cursor/User/settings.json
cursor/keybindings.json   -> ~/Library/Application Support/Cursor/User/keybindings.json
cursor/extensions.txt      # installed extensions

On Linux the User dir is ~/.config/Cursor/User/ instead.

Reinstall extensions (./install.sh --cursor-extensions does this for you):

xargs -n1 cursor --install-extension < cursor/extensions.txt

Not tracked: ~/.cursor/ (extensions, plans, projects, chat state, argv.json) — machine-local state, regenerated by Cursor.

Claude Code

claude/settings.json      -> ~/.claude/settings.json

Plugins and marketplaces are declared inside settings.json (enabledPlugins / extraKnownMarketplaces), so Claude Code reinstalls them on first run. The rest of ~/.claude/ (sessions, history, projects, caches) is local state and deliberately not tracked.

opencode + llama.cpp

Runs opencode against a local model, no API key and no network. Sized for a 16 GB M1 Pro. The runtime is llama-server from llama.cpp; Ollama is kept installed as a fallback but is no longer what opencode talks to.

opencode/opencode.json                 -> ~/.config/opencode/opencode.json
llamacpp/llama-server.sh               -> ~/.config/llamacpp/llama-server.sh
llamacpp/com.maruan.llama-server.plist -> ~/Library/LaunchAgents/  (rendered, not linked)
ollama/Modelfile.coder-q3              -> ~/.config/ollama/Modelfile.coder-q3   (fallback)

Setup on a new machine:

brew install llama.cpp
mkdir -p ~/models/gguf
curl -L -o ~/models/gguf/Qwen3.5-9B-UD-Q4_K_XL.gguf \
  https://huggingface.co/unsloth/Qwen3.5-9B-GGUF/resolve/main/Qwen3.5-9B-UD-Q4_K_XL.gguf
./install.sh --only llamacpp,opencode

install.sh renders the LaunchAgent and bootstraps it, so the server comes up on login and stays up. Then opencode in any project picks the local model up from the config. aliasrc defines llama-restart, llama-log and llama-health for the server, and oc for opencode.

Why llama.cpp and not Ollama

Ollama runs llama.cpp underneath, so this is not a speed argument — it is about one flag. Check what Ollama actually launches:

ps aux | grep llama-server
#  ... --no-jinja --chat-template chatml ...

--no-jinja discards the chat template baked into the model and substitutes a generic ChatML one. That template is what defines the <tool_call> wrapper, and llama.cpp selects its per-model tool-call parser from it. Without it there is nothing to parse tool calls out of, so they come back as prose in the message body and opencode never executes them.

That is the whole reason Qwen2.5-Coder "did not support tools" here, which the previous version of this README recorded as a property of the model. It was not — it was the runtime throwing the template away. Running llama-server directly with --jinja (the default in current builds) returns a structured tool_calls field.

The same flag removes the other hack: Modelfile.coder-q3 had to inline a hand-edited copy of Qwen's template to force thinking off, because Ollama's think: false only works on /api/chat and is ignored on the OpenAI-compatible /v1 endpoint opencode uses. llama.cpp has --reasoning off, which drives the template's own switch. No forked template to maintain.

The model

Qwen3.5-9B at UD-Q4_K_XL — 5.97 GB of weights, ~8.5 GB resident with a 32k context. It replaces Qwen3 8B: same size class, but tool-calling and coding were specifically improved in the 3.5 release, and it does not need the template surgery.

The 30B-class models that are genuinely strong at agentic coding (Qwen3-Coder-30B-A3B, Devstral-24B) need ~18 GB at Q4. Q3 fits on paper at ~13 GB but leaves nothing for KV cache or macOS and swaps. 9B dense is the ceiling on this machine — that is the binding constraint, not the config.

llamacpp/llama-server.sh explains every flag inline.

aliasrc defines oc as opencode --auto, which approves every permission that is not explicitly denied — no prompting. Plain opencode still asks before editing, since opencode.json sets permission.edit to ask. Note that in headless opencode run without --auto, that ask silently skips every edit — exit 0, file unchanged, no error.

Keeping it fast

Three things dominate latency, all fixed here:

  • The model unloading when idle. Under Ollama this meant reloading 5 GB on the next prompt after 5 minutes, and the fix (OLLAMA_KEEP_ALIVE=2h in Homebrew's plist) got overwritten every time the service restarted or upgraded. llama-server holds the model for as long as it runs, and the LaunchAgent under com.maruan is hand-written, so nothing regenerates it.
  • Tool definitions were 54% of the prompt — 5,297 tokens of the ~7,750 sent every step, against 2,456 for the actual system prompt. opencode.json disables task, skill, todowrite and webfetch, cutting the prompt 30% to ~5,400 tokens. Re-enable todowrite first if you want one back; it is the cheapest of the four.
  • Reprocessing the prompt when its middle changes. Agent prompts shift in the middle, not at the end — a file listing or timestamp moves and everything after it does too, which defeats a plain prefix cache. --cache-reuse 256 lets the server KV-shift around the changed chunk.

Quantising the KV cache (q8_0 vs f16) makes no measurable difference to speed — it is kept only because it saves 2.2 GB.

Do not leave Ollama running alongside it. With OLLAMA_KEEP_ALIVE=2h its server holds ~8 GB for two hours after any use, and 8 GB plus llama.cpp's 8.5 GB does not fit in 16 GB — the machine swaps and both get slow. brew services stop ollama; start it again only when actually falling back.

Measured on an M1 Pro

Against the previous Ollama + Qwen3 8B setup, same machine, same opencode config:

Ollama / Qwen3 8B llama.cpp / Qwen3.5 9B
prompt processing ~111 tok/s ~209 tok/s
generation ~24 tok/s ~19 tok/s
cold ~4.4k-token prompt 21 s
warm follow-up turn ~3 s ~4.5 s
correct one-function edits 3 / 8 8 / 8
thinking tokens emitted 0 (forked template) 0 (--reasoning off)

Generation is slightly slower because this is a 9B at a fatter quant, not an 8B at Q4_K_M. Prompt processing — which is most of the wait, since every agent turn re-sends a ~5.4k-token prompt — nearly doubles.

Known rough edges

The edit corruption that dominated the Ollama setup is gone. 8 of 8 runs of the same one-function edit produced code that parsed and behaved correctly, against 3 of 8 before. The specific failures that are no longer reproducible:

  • Literal \n written into the file, from the model double-escaping newlines in tool arguments.
  • Indentation flattened, giving an IndentationError.
  • No edit at all: reads the file, answers, changes nothing.

Most of that was never a sampling problem — it was the generic ChatML template mangling tool arguments on the way out. Lowering the temperature only ever masked it.

What has not changed:

  • This is still a 9B model. It is good at single-file, well-specified edits and gets vaguer the larger the task. It is not a substitute for a frontier model on anything architectural.
  • opencode --auto (aliased to oc) approves everything, including the loop detector. That is the tradeoff being made deliberately — run it in a git repo and read every diff.

Reads and questions are reliable. Treat edits as a draft to review, not as work that is done.

Checking it

llama-health                       # {"status":"ok"}
llama-log                          # tail the server log
llama-restart                      # after editing llama-server.sh

If tool calls come back as prose in the message body rather than as a tool_calls field, the template is not being used — check that --jinja is still in effect and that opencode is pointed at port 8080, not at Ollama.

Zotero

zotero/prefs.js           # curated prefs, append to the profile's prefs.js
zotero/plugins.txt        # installed plugins (reinstall from their releases)
zotero/styles/nature.csl  -> ~/Zotero/styles/

Prefs live in ~/Library/Application Support/Zotero/Profiles/<profile>.default/prefs.js. Quit Zotero before editing it, or drop the lines into a user.js next to it.

The library itself (~/Zotero/zotero.sqlite, storage/) is not tracked — that is data, handled by Zotero sync. Credentials/API keys are excluded from prefs.js on purpose.

About

My configuration files

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages