Skip to content

Instantly share code, notes, and snippets.

@timm
Last active June 27, 2026 01:42
Show Gist options
  • Select an option

  • Save timm/431f18a0f4010117892810cb3585c076 to your computer and use it in GitHub Desktop.

Select an option

Save timm/431f18a0f4010117892810cb3585c076 to your computer and use it in GitHub Desktop.
konfig

AuthorLanguageLicensePurpose

Portable, self-erasing boilerplate for all my gists: one Makefile (help, doctor, lint, push, pdf, tuned shells/editors/mux), one bashrc, init.lua + init.el, one tmux.conf, plus isolated nvim/micro configs. A project repo is just knobs plus include $(KONFIG)/Makefile. Every file these tools generate is buried under a .../konfig/... directory — never your real dotfiles — so make death wipes the lot and leaves your machine spotless.

# install and test-drive from any sibling gist
git clone http://tiny.cc/konfig
git clone http://tiny.cc/luamine luamine && cd luamine
make help
image

Sections: NAME | SYNOPSIS | OPTIONS | OUTPUT | SEE ALSO | LICENSE | AUTHOR

Files: Makefile | bashrc | init.lua | init.el | tmux.conf | micro.settings.json | micro.bindings.json | micro.lisp.yaml | style_code.md | style_gist.md | newgist.md | make.md | bash.md | nvim.md | micro.md | tmux.md | banner.sh | help.awk | hist.awk | install.sh | Brewfile

NAME

konfig - shared Makefile + dotfiles for sibling gist repos

SYNOPSIS

# in a project repo's Makefile: knobs, guard, include
KONFIG ?= ../konfig
APP    := project
...
include $(KONFIG)/Makefile

make help|doctor|check|test|push|hist|sh|vi|mux|pdf|death

OPTIONS

Knobs a repo sets before the include (see style_gist.md):

  APP    project name (tmux socket, NVIM_APPNAME, banner)
  MAIN   primary source file        (code repos)
  EXT    source extension           (code repos)
  LANG   language name              (code repos)
  LINT   lint command               (code repos)
  TOOLS  tool:why pairs for doctor
  PKG    packages doctor suggests installing

Tool tutorials: make.md, bash.md, nvim.md, micro.md, tmux.md.
Style law for sibling gists: style_code.md, style_gist.md.
Spinning up a new gist (tool or data): newgist.md.

OUTPUT

make help    banner + every `target: ## desc` line
make doctor  tool checklist with install hints
make sh      tuned bash    (banner, git prompt, vi-mode)
make vi      isolated nvim (catppuccin + tree; --clean)
make mux     private-socket tmux; `make claude` = 3 panes
make pdf     a2ps pretty-print -> ~/tmp/konfig/*.pdf
make death   wipe every generated .../konfig/ dir (2x confirm)

Editors (aliases inside `make sh`): vi=nvim  m=micro.
All state isolated under ~/.{cache,config,local}/konfig/ + ~/tmp/konfig
— nothing touches your real dotfiles; `make death` erases it all.

SEE ALSO

luamine   http://tiny.cc/luamine   Lua data mining
optimiz   http://tiny.cc/optimiz   example datasets
fft       http://tiny.cc/fft       FFTs + regression trees
repltut   http://tiny.cc/repltut   REPL tutorial prompts

LICENSE

MIT. https://choosealicense.com/licenses/mit/

AUTHOR

Tim Menzies <timm@ieee.org>
#!/usr/bin/env bash
# colorful banner printer. usage: banner.sh FILE
# each line a catppuccin-ish hue, bold. last line (quote) lands green.
f="$1"; [[ -f $f ]] || exit 0
printf '\033[2J\033[3J\033[H' # clear screen + scrollback, cursor home
cols=(212 213 177 141 147 117 84) # pink magenta mauve lavender blue green
i=0
while IFS= read -r line; do
printf '\033[1;38;5;%sm%s\033[0m\n' "${cols[i % ${#cols[@]}]}" "$line"
((i++))
done < "$f"
_ __ _
| | __ ___ _ __ / _| (_) __ _
| |/ / / _ \ | '_ \ | |_ | | / _` |
| < | (_) | | | | | | _| | | | (_| |
|_|\_\ \___/ |_| |_| |_| |_| \__, |
|___/
all my config files. http://tiny.cc/konfig
"plain text, sharp tools, no magic."

bash.md — bash, as konfig tunes it

Konfig's bashrc is not your login shell. It layers a tuned interactive shell on top of it, started per-repo via make sh (which runs bash --rcfile $(KONFIG)/bashrc -i).

start it

make sh          # from any gist repo

You get: banner art, prompt with git branch, vi-mode line editing, short aliases. Exit with exit or ctrl-d; you fall back to your normal shell.

the prompt

konfig/src main* [42]$

parent/cwd      cyan; last two path parts only
main            git branch, yellow; '*' = dirty tree
[42]            bash history number (recall: !42)

line editing is vi-mode

set -o vi. ESC then h/j/k/l, b w e, 0 $, dd, cw... ESC-k walks history; /text searches it. INSERT mode is default, so typing works normally until you hit ESC.

aliases

p     python3 -B $MAIN     run the repo entry point
c     make check           lint
vi    nvim  --clean -u $KONFIG/init.lua   (state -> konfig/nvim)
m     micro -config-dir ~/.config/konfig/micro  (catppuccin)
tmux  tmux -f $KONFIG/tmux.conf
gs    git status -s
gd    git diff
gl    git log --oneline -20
ll    ls -la
cat   bat --paging=never   syntax-colored cat
less  bat

history tricks

!42        rerun history line 42 (the [42] in the prompt)
!!         rerun last command
!$         last word of last command
ctrl-r     (in insert mode) is replaced by ESC /text in vi-mode

per-repo overrides

Put repo-specific aliases in bashrc.local next to the Makefile; it is sourced last, so it wins:

# bashrc.local
alias r='lua luamine.lua --all'

Silent if missing. Never edit konfig's bashrc for one repo.

why not source ~/.bashrc?

PATH and env are already inherited from the parent login shell. Re-sourcing would clobber the prompt, the vi alias, and the banner. KONFIG/APP/MAIN/BANNER are consumed then unset so child shells (tmux, claude) don't inherit stale paths.

# tuned interactive bash. source via: bash --rcfile bashrc -i
# env in: KONFIG (this dir), APP (NVIM_APPNAME), MAIN (py entry), BANNER (art)
set -o vi
export BASH_SILENCE_DEPRECATION_WARNING=1 # mute macOS "default shell is zsh" notice
export PATH="$HOME/.local/bin:$PATH"
# bat: groovy. Catppuccin Mocha matches banner purple/green. grid+rule decor.
export BAT_THEME="Catppuccin Mocha"
export BAT_STYLE="numbers,changes,header,grid,snip"
# do NOT source ~/.bashrc: PATH/env already inherited from the parent
# login shell. Sourcing it clobbers PS1 (PROMPT_COMMAND), the vi alias,
# and drags in the fossil banner.
# prompt: parent/cwd in cyan + git branch (yellow, * if dirty) + [histno]
__gp() {
local b=$(git branch --show-current 2>/dev/null)
[[ -z $b ]] && return
[[ -n $(git status --porcelain 2>/dev/null) ]] && b="$b*"
echo " $b"
}
__pw() { pwd | awk -F/ '{print $(NF-1)"/"$NF}'; }
PS1='\[\e[36m\]$(__pw)\[\e[33m\]$(__gp) \[\e[0m\][\!]\$ '
alias p="python3 -B ${MAIN:-main.py}" c="make check"
alias ll='ls -la' gs='git status -s' gd='git diff' gl='git log --oneline -20'
alias cat='bat --paging=never' less='bat' # groovy syntax color
# vi: real file in $KONFIG. NVIM_APPNAME=konfig/nvim puts ALL nvim state
# (config/data/state/cache) under a konfig/ segment, never the real ~ nvim dirs.
alias vi="NVIM_APPNAME=konfig/nvim nvim --clean -u \"${KONFIG:-.}/init.lua\""
# e: emacs in terminal (-nw), -Q skips real ~/.emacs.d, --init-directory parks
# elpa/state under ~/.config/konfig/emacs (needs emacs 29+; like NVIM_APPNAME above).
alias e="emacs -nw -Q --init-directory \"$HOME/.config/konfig/emacs\" -l \"${KONFIG:-.}/init.el\""
# tmux: repo config (path baked now; KONFIG unset below). No recursion: bash
# won't re-expand the same word.
alias tmux="tmux -f \"${KONFIG:-.}/tmux.conf\""
# free C-s (save) from terminal XOFF flow-control, so micro/nvim can bind it.
stty -ixon 2>/dev/null
# m: simple micro. micro needs a config DIR but the gist is flat, so build the
# dir under ~/.config/konfig/micro at startup, symlinking the flat repo files in.
# plugins/themes micro downloads land there too -- never the real ~/.config/micro.
__md="$HOME/.config/konfig/micro"
mkdir -p "$__md/colorschemes" "$__md/syntax"
ln -sf "${KONFIG:-$PWD}/micro.settings.json" "$__md/settings.json"
ln -sf "${KONFIG:-$PWD}/micro.bindings.json" "$__md/bindings.json"
ln -sf "${KONFIG:-$PWD}/micro.lisp.yaml" "$__md/syntax/lisp.yaml"
ln -sf "${KONFIG:-$PWD}/catppuccin-mocha.micro" "$__md/colorschemes/catppuccin-mocha.micro"
# plugins: install any missing into the isolated dir (first run only; needs net).
# tree=F5 sidebar, fzf=F6 fuzzy-open, aspell=F8 spell, detectindent=auto tabs.
for __p in filemanager fzf aspell detectindent; do
[ -d "$__md/plug/$__p" ] || micro -config-dir "$__md" -plugin install "$__p" >/dev/null 2>&1
done
alias m="micro -config-dir \"$__md\""
unset __md __p
# graphic on top, colorful
[ -f "$BANNER" ] && bash "${KONFIG:-.}/banner.sh" "$BANNER"
# shortcuts under it
printf '\033[1;38;5;141m shortcuts \033[0m'
for kv in "p:run" "c:check" "vi:edit" "e:emacs" "m:micro" "tmux:mux" "gs:status" "gd:diff" "gl:log" "ll:ls"; do
printf '\033[38;5;84m%s\033[0m\033[38;5;245m=%s \033[0m' "${kv%%:*}" "${kv##*:}"
done
printf '\n\n'
# per-repo overrides (sourced last so they win). silent if missing.
[ -f "$PWD/bashrc.local" ] && source "$PWD/bashrc.local"
# stop fossil leak: aliases + banner already consumed these. Don't let
# child shells / tmux / claude inherit stale paths from this session.
unset BANNER APP MAIN KONFIG
# Brewfile — konfig. regenerate: ./install.sh dump
# install all: ./install.sh (or: brew bundle)
tap "homebrew/cask-fonts"
tap "homebrew/services"
tap "koekeishiya/formulae"
tap "railwaycat/emacsmacport"
tap "sinelaw/fresh"
tap "xwmx/taps"
brew "a2ps"
brew "asciidoc"
brew "asciidoctor"
brew "asciinema"
brew "asciiquarium"
brew "aspell"
brew "berkeley-db"
brew "bib-tool"
brew "bibclean"
brew "bibtex-tidy"
brew "bison"
brew "bpytop"
brew "bsdmake"
brew "btop"
brew "cabal-install"
brew "calcurse"
brew "chezscheme"
brew "chicken"
brew "chruby"
brew "clisp"
brew "cmatrix"
brew "coreutils"
brew "crystal"
brew "curl"
brew "docker"
brew "duf"
brew "dust"
brew "emacs"
brew "enscript"
brew "entr"
brew "erlang"
brew "eza"
brew "feedgnuplot"
brew "fennel"
brew "ffmpeg"
brew "figlet"
brew "fpc"
brew "fswatch"
brew "fzf"
brew "gawk"
brew "git-filter-repo"
brew "glances"
brew "global"
brew "glow"
brew "gnuchess"
brew "gnu-smalltalk"
brew "go"
brew "groff"
brew "groovy"
brew "gtksourceview3"
brew "haskell-language-server"
brew "helix"
brew "highlight"
brew "htop"
brew "hugo"
brew "hwatch"
brew "imagemagick"
brew "imagemagick@6"
brew "ispell"
brew "janet"
brew "jed"
brew "joe"
brew "kakoune"
brew "koekeishiya/formulae/skhd"
brew "koekeishiya/formulae/yabai"
brew "kotlin"
brew "lf"
brew "libffi"
brew "lolcat"
brew "lsd"
brew "lua-language-server"
brew "lua@5.3"
brew "luarocks"
brew "macvim"
brew "markdown-toc"
brew "mdcat"
brew "mdfried"
brew "mdp"
brew "micro"
brew "midnight-commander"
brew "mle"
brew "nano"
brew "nbsdgames"
brew "ncdu"
brew "ne"
brew "neovim"
brew "nim"
brew "nnn"
brew "ocaml"
brew "openblas"
brew "ox"
brew "par"
brew "parallel"
brew "pipes-sh"
brew "pipx"
brew "poppler"
brew "presenterm"
brew "prettier"
brew "purescript"
brew "pylint"
brew "pyright"
brew "python@3.10"
brew "qt"
brew "qt@5"
brew "railwaycat/emacsmacport/emacs-mac@29"
brew "ranger"
brew "rlwrap"
brew "ruby-install"
brew "ruff"
brew "rust-script"
brew "sbcl"
brew "scheme48"
brew "sinelaw/fresh/fresh"
brew "starship"
brew "swi-prolog"
brew "tectonic"
brew "terminator"
brew "tidy-html5"
brew "tmux"
brew "tree"
brew "twine"
brew "unzip"
brew "vifm"
brew "vim"
brew "vitetris"
brew "watch"
brew "wget"
brew "xwmx/taps/nb"
brew "yazi"
brew "youplot"
brew "zellij"
brew "zim"
brew "zlib"
cask "cabal"
cask "claude"
cask "cursor"
cask "discord"
cask "far2l"
cask "font-fira-code"
cask "font-fira-code-nerd-font"
cask "fresh"
cask "geany"
cask "golly"
cask "joplin"
cask "kitty"
cask "lapce"
cask "lastpass"
cask "love"
cask "mactex"
cask "markedit"
cask "qownnotes"
cask "racket"
cask "save-hollywood"
cask "spotify"
cask "warp"
cask "webviewscreensaver"
cask "wezterm"
cask "zed"
color-link default "#cdd6f4,#1e1e2e"
color-link comment "#9399b2"
color-link selection "#cdd6f4,#45475a"
color-link hlsearch "#94e2d5"
color-link identifier "#89b4fa"
color-link identifier.class "#89b4fa"
color-link identifier.var "#89b4fa"
color-link constant "#fab387"
color-link constant.number "#fab387"
color-link constant.string "#a6e3a1"
color-link symbol "#f5c2e7"
color-link symbol.brackets "#f2cdcd"
color-link symbol.tag "#89b4fa"
color-link type "#89b4fa"
color-link type.keyword "#f9e2af"
color-link special "#f5c2e7"
color-link statement "#cba6f7"
color-link preproc "#f5c2e7"
color-link underlined "#89dceb"
color-link error "bold #f38ba8"
color-link todo "bold #f9e2af"
color-link diff-added "#a6e3a1"
color-link diff-modified "#f9e2af"
color-link diff-deleted "#f38ba8"
color-link gutter-error "#f38ba8"
color-link gutter-warning "#f9e2af"
color-link scrollbar "#9399b2"
color-link statusline "#cdd6f4,#181825"
color-link tabbar "#cdd6f4,#181825"
color-link indent-char "#45475a"
color-link line-number "#45475a"
color-link current-line-number "#b4befe"
color-link cursor-line "#313244,#cdd6f4"
color-link color-column "#313244"
color-link type.extended "default"

CLAUDE.md

Before editing any file in this repo or in sibling gist repos (../semble, ../optimiz, etc.), read both:

Both are short. Follow them.

# gists.mk -- konfig-only bootstrap of the sibling repos.
# included by konfig/Makefile ONLY when make runs inside konfig, so these
# targets stay out of the shared boilerplate that every sibling includes
# (and out of their `make help`). `make <name>` clones the repo, or pulls if
# it is already there; `make gists` does the lot.
# ~/gists -- dir holding the sibling gists. comment on its OWN line: an inline
# # comment would leave trailing spaces inside the path (see KONFIG strip note).
GROOT ?= $(abspath $(KONFIG)/..)
# $(call sync,clone-url,dest): clone if the dest is absent, else fast-forward pull
sync = @if [ -d "$(2)/.git" ]; then printf 'pull %s\n' "$(2)"; git -C "$(2)" pull --ff-only -q; \
else printf 'clone %s\n' "$(1)"; git clone -q "$(1)" "$(2)"; fi
ezr: ## clone/pull ezr
$(call sync,http://tiny.cc/ezr,$(GROOT)/ezr)
ezr2: ## clone/pull ezr2 (landscape + dual-mode tree)
$(call sync,http://tiny.cc/ezr2,$(GROOT)/ezr2)
fairnez: ## clone/pull fairnez
$(call sync,http://tiny.cc/fairnez,$(GROOT)/fairnez)
nuff: ## clone/pull nuff (tiny python tricks lib)
$(call sync,http://tiny.cc/nuff,$(GROOT)/nuff)
skape: ## clone/pull skape (FastMap landscape active learner)
$(call sync,http://tiny.cc/skape,$(GROOT)/skape)
gistsite: ## clone/pull gistsite (render gists -> static html catalog)
$(call sync,http://tiny.cc/gistsite,$(GROOT)/gistsite)
xomo: ## clone/pull xomo (cocomo-ii + coqualmo: effort/defects/risk)
$(call sync,http://tiny.cc/xomo,$(GROOT)/xomo)
kah-lua: ## clone/pull kah-lua
$(call sync,http://tiny.cc/kah-lua,$(GROOT)/kah-lua)
klassif: ## clone/pull klassif (classification CSVs)
$(call sync,http://tiny.cc/klassif,$(GROOT)/klassif)
textz: ## clone/pull textz (text-mining CSVs)
$(call sync,http://tiny.cc/textz,$(GROOT)/textz)
lithp: ## clone/pull lithp (was lisp-)
$(call sync,http://tiny.cc/lithp,$(GROOT)/lithp)
luamine: ## clone/pull luamine (slug: tiny.cc/lull)
$(call sync,http://tiny.cc/lull,$(GROOT)/luamine)
luk: ## clone/pull luk
$(call sync,http://tiny.cc/luk,$(GROOT)/luk)
optimiz: ## clone/pull optimiz
$(call sync,https://gist.github.com/timm/078f9287b9871124c63be19f8c9ec5de,$(GROOT)/optimiz)
repltut: ## clone/pull repltut
$(call sync,http://tiny.cc/repltut,$(GROOT)/repltut)
sand-box: ## clone/pull sand-box
$(call sync,http://tiny.cc/sand-box,$(GROOT)/sand-box)
fyi: ## clone/pull fyi (website; lives in ~/gits/timm)
$(call sync,https://github.com/timm/fyi,$(HOME)/gits/timm/fyi)
gists: ezr ezr2 fairnez nuff skape gistsite xomo kah-lua klassif textz lithp luamine luk optimiz repltut sand-box fyi ## clone/pull ALL the above
# self-doc for Makefiles. run: gawk -f help.awk $(MAKEFILE_LIST)
# targets = lines like `name: ## description`
# defaults = lines like `VAR ?= value # description`
/^[~a-zA-Z0-9_%.\/ -]+:.*?##/ {
split($0, a, ":.*?##")
t[++tn] = sprintf(" \033[36m%-20s\033[0m %s", a[1], a[2])
}
match($0, /^([A-Za-z][A-Za-z0-9]*)[ \t]*\?=[ \t]*([^#]*[^# \t])[ \t]*#[ \t]*(.+)/, m) {
d[++dn] = sprintf(" \033[36m%-8s\033[0m = %-30s %s", m[1], m[2], m[3])
}
END {
printf "\nUsage:\n make \033[36m<target>\033[0m [VAR=val ...]\n\ntargets:\n"
n = asort(t); for (i = 1; i <= n; i++) print t[i]
if (dn) {
printf "\ndefaults:\n"
n = asort(d); for (i = 1; i <= n; i++) print d[i]
}
print " "
}
# paragraph line-count histogram + cumulative %. comments stripped upstream.
# only paragraphs whose first line starts with def/function are counted,
# so module docstrings, data tables, inline strings don't inflate.
# run: cat src | grep -Ev '^[[:space:]]*(#)' | gawk -f hist.awk
BEGIN { RS = ""; FS = "\n" }
$1 ~ /^(def |function |local function )/ { d[NF]++; tot++ }
END {
n = asorti(d, k, "@ind_num_asc"); all = 0
for (i = 1; i <= n; i++) {
all += d[k[i]]; s = ""
for (j = 0; j < d[k[i]]; j++) s = s "#"
printf "%3d %3d %s\n", k[i], int(all * 100 / tot), s
}
}
;; Emacs config: evil + org-babel + Common Lisp (SLY) + magit (launch: emacs -Q -l this)
;; package archives + use-package (auto-install anything missing)
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless package-archive-contents (package-refresh-contents))
(setq use-package-always-ensure t)
;; core editor: menu bar kept, tool/scroll bars off, line numbers, bigger font, dark theme (nvim mocha)
;; tool-bar-mode/scroll-bar-mode are VOID in terminal builds -- guard or init aborts.
(menu-bar-mode 1)
(when (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(xterm-mouse-mode 1) ; mouse clicks (incl. menu bar) in terminal emacs
(setq inhibit-startup-screen t ring-bell-function 'ignore)
(global-display-line-numbers-mode 1)
(set-face-attribute 'default nil :height 140)
(use-package catppuccin-theme :config (load-theme 'catppuccin t))
;; vim keys everywhere
(use-package evil :init (setq evil-want-keybinding nil) :config (evil-mode 1))
(use-package evil-collection :after evil :config (evil-collection-init))
;; minibuffer: vertical candidate list + inline annotations
(use-package vertico :config (vertico-mode 1))
(use-package marginalia :config (marginalia-mode 1))
;; org + babel: run python/shell/lisp blocks inline with C-c C-c, pass data between langs
(use-package org)
(org-babel-do-load-languages
'org-babel-load-languages
'((python . t) (shell . t) (lisp . t) (emacs-lisp . t)))
(setq org-confirm-babel-evaluate nil)
;; Common Lisp REPL (needs sbcl on PATH): M-x sly
(use-package sly :init (setq inferior-lisp-program "sbcl"))
;; git porcelain: M-x magit-status
(use-package magit)
;; per-repo overrides (loaded last so they win). silent if missing.
(load (expand-file-name "init.local.el" default-directory) t)
-- Neovim config: catppuccin-mocha + nvim-tree (launch: nvim --clean -u this)
vim.g.mapleader = " "
vim.o.termguicolors = true
vim.o.number = true
vim.o.cursorline = true
vim.o.expandtab = true
vim.o.shiftwidth, vim.o.tabstop, vim.o.softtabstop = 2, 2, 2
vim.o.ignorecase, vim.o.smartcase = true, true
vim.o.clipboard = "unnamedplus"
vim.o.autoread = true -- reload disk changes
vim.o.updatetime = 250 -- CursorHold fires faster
-- autoread needs a poll: re-check on idle / buffer-enter / focus.
vim.api.nvim_create_autocmd(
{ "FocusGained", "BufEnter", "CursorHold", "CursorHoldI" },
{ callback = function()
if vim.fn.mode() ~= "c" and vim.fn.getcmdwintype() == "" then
vim.cmd("checktime")
end
end })
-- plugins via vim.pack (nvim 0.12). --clean drops site from packpath; re-add it.
vim.g.loaded_netrw, vim.g.loaded_netrwPlugin = 1, 1 -- nvim-tree replaces netrw
vim.opt.packpath:prepend(vim.fn.stdpath("data") .. "/site")
vim.pack.add({
"https://github.com/catppuccin/nvim",
"https://github.com/nvim-tree/nvim-web-devicons",
"https://github.com/nvim-tree/nvim-tree.lua",
})
vim.cmd.colorscheme("catppuccin-mocha")
-- file manager: 25% sidebar, nerd-font icons
require("nvim-tree").setup({
view = { width = "25%" },
renderer = { group_empty = true, highlight_git = true },
filters = { dotfiles = false },
})
vim.keymap.set("n", "<leader>e", "<cmd>NvimTreeToggle<CR>") -- toggle sidebar
-- per-repo overrides (loaded last so they win). silent if missing.
pcall(dofile, vim.fn.getcwd() .. "/init.local.lua")
-- vim.o.termguicolors = true
-- vim.o.expandtab = true
-- vim.o.tabstop = 2
-- vim.o.shiftwidth = 2
-- vim.o.softtabstop = 2
-- vim.o.ignorecase = true
-- vim.o.smartcase = true
--
-- vim.g.maplocalleader = "\\"
--
-- vim.o.relativenumber = true
-- vim.o.cursorline = true
-- vim.o.mouse = "a"
-- vim.o.termguicolors = true
-- vim.o.expandtab = true
-- vim.o.tabstop = 2
-- vim.o.shiftwidth = 2
-- vim.o.softtabstop = 2
-- vim.o.ignorecase = true
-- vim.o.smartcase = true
-- vim.o.scrolloff = 8
-- vim.o.signcolumn = "yes"
-- vim.o.splitbelow = true
-- vim.o.splitright = true
-- vim.o.wrap = true
-- vim.o.linebreak = true
-- vim.o.fillchars = "vert:│,eob:·"
-- vim.o.winborder = "rounded"
-- vim.o.splitkeep = "screen"
-- vim.o.smoothscroll = true
-- vim.o.undofile = true
-- vim.o.timeout = false
-- vim.o.sidescrolloff = 7
-- vim.o.sidescroll = 1
-- vim.o.autoindent = true
-- vim.opt.wildignore:append({
-- "*/node_modules/*","*/tmp/*","*/target/*","*/build/*",
-- })
--
-- local map = vim.keymap.set
-- map("n", "x", '"_x') -- x/X to black hole
-- map("n", "X", '"_X')
-- map("n", "<c-j>", ":m .+1<CR>==") -- move line down
-- map("n", "<c-k>", ":m .-2<CR>==") -- move line up
-- map("t", "<Esc>", [[<C-\><C-n>]]) -- escape terminal
-- map("n", "<leader><tab>", "<c-^>")
-- map("n", "<leader>w", "<c-w>")
--
-- vim.filetype.add({ extension = { mal = "lisp" } })
--
-- vim.api.nvim_create_autocmd("BufReadPost", { -- restore cursor pos
-- callback = function()
-- local m = vim.api.nvim_buf_get_mark(0, '"')
-- if m[1] > 1 and m[1] <= vim.api.nvim_buf_line_count(0) then
-- vim.api.nvim_win_set_cursor(0, m)
-- end
-- end,
-- })
--
-- vim.opt.packpath:prepend(vim.fn.stdpath("data") .. "/site")
-- vim.pack.add({
-- "https://github.com/catppuccin/nvim",
-- "https://github.com/nvim-lualine/lualine.nvim",
-- "https://github.com/zaldih/themery.nvim",
-- "https://github.com/folke/tokyonight.nvim",
-- "https://github.com/rebelot/kanagawa.nvim",
-- "https://github.com/rose-pine/neovim",
-- "https://github.com/EdenEast/nightfox.nvim",
-- })
-- vim.cmd.colorscheme("catppuccin-mocha")
-- vim.cmd("hi! WinSeparator guifg=#7aa2f7 guibg=NONE")
-- vim.cmd("hi! VertSplit guifg=#7aa2f7 guibg=NONE")
-- require("lualine").setup({ options = { theme = "auto" } })
-- require("themery").setup({
-- themes = {
-- "catppuccin-mocha","catppuccin-latte",
-- "tokyonight-storm","tokyonight-day",
-- "kanagawa-wave","kanagawa-dragon",
-- "rose-pine","rose-pine-dawn",
-- "nightfox","duskfox","carbonfox",
-- },
-- livePreview = true,
-- })
--
-- vim.api.nvim_create_autocmd("TextYankPost", {
-- callback = function() vim.hl.on_yank() end,
-- })
--
-- vim.diagnostic.config({
-- virtual_text = false,
-- signs = true,
-- underline = true,
-- update_in_insert = false,
-- severity_sort = true,
-- float = { border = "rounded" },
-- })
--
-- if vim.fn.executable("pyright-langserver") == 1 then
-- vim.lsp.config("pyright", {
-- cmd = {"pyright-langserver", "--stdio"},
-- filetypes = {"python"},
-- root_markers = {"pyproject.toml", ".git"},
-- })
-- vim.lsp.enable("pyright")
-- vim.api.nvim_create_autocmd("LspAttach", {
-- callback = function(a)
-- local b = { buffer = a.buf, silent = true }
-- vim.keymap.set("n", "K", vim.lsp.buf.hover, b)
-- vim.keymap.set("n", "gd", vim.lsp.buf.definition, b)
-- vim.keymap.set("n", "gr", vim.lsp.buf.references, b)
-- vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, b)
-- end,
-- })
-- end
#!/usr/bin/env bash
# install.sh — set up this machine from konfig's Brewfile.
# ./install.sh install everything in ./Brewfile
# ./install.sh dump regenerate ./Brewfile from current brew state
# ./install.sh check list Brewfile entries not yet installed
# Brewfile lists only intentional installs (brew leaves + casks);
# dependencies resolve automatically.
set -euo pipefail
cd "$(dirname "$0")"
have() { command -v "$1" >/dev/null 2>&1; }
# homebrew: install if missing
if ! have brew; then
echo "installing homebrew..."
/bin/bash -c "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# apple silicon puts brew in /opt/homebrew; add to this shell
[ -x /opt/homebrew/bin/brew ] && eval "$(/opt/homebrew/bin/brew shellenv)"
fi
case "${1:-install}" in
dump)
{
echo "# Brewfile — konfig. regenerate: ./install.sh dump"
echo "# install all: ./install.sh (or: brew bundle)"
echo
brew tap | sed 's/^/tap "/; s/$/"/'
echo
brew leaves --installed-on-request | sed 's/^/brew "/; s/$/"/'
echo
brew list --cask | sed 's/^/cask "/; s/$/"/'
} > Brewfile
echo "wrote Brewfile ($(grep -c '^\(brew\|cask\) ' Brewfile) packages)"
;;
check)
brew bundle check --file=Brewfile --verbose || true
;;
install)
brew bundle install --file=Brewfile
echo "done. shell/editor/tmux config: make sh | make vi | make mux"
;;
*)
echo "usage: ./install.sh [install|dump|check]" >&2
exit 1
;;
esac

make.md — make, as konfig uses it

One shared Makefile (this repo) gives every gist the same targets. A repo's own Makefile is just knobs + include $(KONFIG)/Makefile.

the everyday targets

make             same as `make help` (default goal)
make help        banner + every `target: ## desc` line
make doctor      check required tools, print install hints
make check       run $(LINT)
make test        repo-defined; runs every UPPERCASE test rule
make push        git add -A, commit (msg from cli or prompt), push
make push fix typo   <- message straight on the command line
make pushs       commit+push EVERY sibling gist (prompts if dirty)
make pulls       pull every sibling gist (skips dirty ones)
make sh          tuned bash        (see bash.md)
make vi          tuned nvim        (see nvim.md)
make mux         tuned tmux        (see tmux.md)
make hist        paragraph-size histogram of the source
make ~/tmp/foo.pdf   pretty-print foo.$(EXT) via a2ps

a repo Makefile (knobs then include)

KONFIG ?= ../konfig
APP    := luamine          # session/socket/NVIM_APPNAME
MAIN   := luamine.lua      # entry point (p alias)
EXT    := lua
LANG   := lua           # a2ps language
COMMENT:= --            # hist strips these
LINT    = luacheck -- *.lua
TOOLS  := lua:run luacheck:check
PKG    := lua luacheck gawk neovim tmux

$(KONFIG)/Makefile:
	@test -f $@ || { echo "missing konfig: git clone http://tiny.cc/konfig $(KONFIG)"; exit 1; }
include $(KONFIG)/Makefile

Data-only repos: keep just KONFIG, APP, PKG, the guard, the include.

self-documenting help

make help greps the Makefile itself:

target: ## one-line description     -> shown by help
VAR ?= default   # for <target>     -> shown under that target

Document a target by writing the ## comment; nothing else needed.

tests: one rule per test, UPPERCASE

PARSE: ## test: csv parser round-trips
	@lua tests/parse.lua

test:  ## run every UPPERCASE rule
	@gawk -F: '/^[A-Z][A-Z_]*:[^=]/ {print $$1}' $(MAKEFILE_LIST) | \
	  sort -u | while read t; do \
	    printf "\n=== %s ===\n" "$$t"; $(MAKE) -s $$t; done

Add a test = add an UPPERCASE rule. test discovers it by grep; no central list to maintain.

make survival rules

recipes are TABS, not spaces (the classic error)
$$ in a recipe = shell $ (make eats one)
?=  set only if not already set (env/cli can override)
:=  evaluate now;  =  evaluate at use
@   prefix = don't echo the command
make -s       silent     make -n   dry run (print, don't do)
make V=1 t    pass variable V to target t

paths

Cross-repo paths hang off $(DOOT) (gists root, default: parent of konfig). Never hardcode ../sibling outside the KONFIG ?= ../konfig bootstrap line. See style_gist.md.

# vim: ts=2 sw=2 sts=2 et :
# shared make boilerplate. doubles as konfig's own Makefile.
# a repo Makefile sets a few knobs then: include $(KONFIG)/Makefile
# knobs (all optional, defaults below):
# APP MAIN EXT LANG SRC COMMENT LINT TOOLS PKG KONFIG
SHELL := /bin/bash
# self-locate: dir of this file (= "." standalone, repo's path when included)
KONFIG ?= $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
# APP/MAIN/BANNER: ignore env (fossil-leak from prior `make sh`). Repo
# Makefile assignments (origin=file) keep winning; only origin=environment
# is overridden back to default.
ifeq ($(origin APP),environment)
override APP := $(notdir $(CURDIR))
endif
ifeq ($(origin MAIN),environment)
override MAIN := main.py
endif
ifeq ($(origin BANNER),environment)
override BANNER := banner.txt
endif
APP ?= $(notdir $(CURDIR)) # NVIM_APPNAME / tmux socket / session
MAIN ?= main.py # entry point (p alias in bashrc)
EXT ?= py # source file extension
LANG ?= python # a2ps pretty-print language
SRC ?= *.$(EXT) # source glob (hist, pdf, lint)
COMMENT ?= \#|--|// # comment regex (hist strips these)
LINT ?= ruff check $(SRC) # check target command
TOOLS ?= # extra doctor checks: "cmd:use cmd:use"
PKG ?= gawk git neovim tmux micro # doctor install hint
BANNER ?= banner.txt # ascii art shown atop help (if present)
# make keeps trailing spaces before #, which break path concat; strip them
override KONFIG := $(strip $(KONFIG))
# DOOT: gists root (dir holding konfig + sibling gists). env wins; else
# parent of konfig. all cross-repo paths hang off this -- see style_gist.md.
DOOT ?= $(abspath $(KONFIG)/..)
override APP := $(strip $(APP))
override MAIN := $(strip $(MAIN))
OPEN := $(shell command -v open 2>/dev/null \
|| command -v xdg-open 2>/dev/null \
|| echo true)
need = @command -v $(1) >/dev/null || { \
printf "missing: %s (needed for %s)\n" $(1) $(2); \
exit 1; }
konfig = @test -d $(KONFIG) || { \
printf "missing dir: %s\n" "$(KONFIG)"; \
printf "clone: git clone http://tiny.cc/konfig %s\n" "$(KONFIG)"; \
exit 1; }
# base tools these targets need, plus per-repo TOOLS
ALLTOOLS := gawk:help/hist git:push nvim:vi/sh tmux:mux micro:m $(TOOLS)
.DEFAULT_GOAL := help
## help -------------------------------------------------------
help: ## show help
@bash $(KONFIG)/banner.sh $(BANNER) 2>/dev/null || true
@gawk -f $(KONFIG)/help.awk $(MAKEFILE_LIST)
@echo " "
## doctor -----------------------------------------------------
doctor: ## check required tools
@for e in $(ALLTOOLS); do \
c=$${e%%:*}; use=$${e##*:}; \
if command -v $$c >/dev/null; then \
printf " \033[32mok\033[0m %-10s used by: %s\n" "$$c" "$$use"; \
else \
printf " \033[31mXX\033[0m %-10s missing: %s\n" "$$c" "$$use"; fi; done
@printf "\nmacOS: brew install $(PKG)\n"
@printf "linux: apt install $(PKG)\n"
## install ----------------------------------------------------
install: ## how to install packages (runs nothing)
@printf "package install is a separate step. run:\n\n"
@printf " ./install.sh # install from Brewfile\n"
@printf " ./install.sh dump # regenerate Brewfile from this machine\n"
@printf " ./install.sh check # list Brewfile pkgs not yet installed\n\n"
## check ------------------------------------------------------
check: ## lint source ($(LINT))
@$(LINT) || true
## push -------------------------------------------------------
push: ## add+commit+push+status; msg from cli (make push my note) else prompts
@git add -A
@m="$(filter-out $@,$(MAKECMDGOALS))"; \
[ -z "$$m" ] && { printf "commit msg (empty=save): "; read m </dev/tty; }; \
git commit -m "$${m:-save}" || true
@git push
@git status
%: # swallow the message words so make won't error
@:
# walk sibling gists, run shell fn `repo` (defined by caller) inside each
eachgist = for d in $$(cd .. && ls -d */ 2>/dev/null); do d=$${d%/}; \
[ -d "../$$d/.git" ] || continue; \
printf "\n=== %s ===\n" "$$d"; \
( cd ../$$d && repo ); \
done
pushs: ## commit+push every sibling gist; prompts only if dirty
@repo(){ \
if [ -n "$$(git status --porcelain)" ]; then \
printf " msg (empty=skip): "; read m </dev/tty; \
[ -z "$$m" ] && { echo " skipped"; return 0; }; \
git add -A && git commit -m "$$m" && git push; \
elif [ "$$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0)" != "0" ]; then \
git push; \
else echo " clean + synced"; fi; \
}; $(eachgist)
pulls: ## git pull every sibling gist (skips dirty repos)
@repo(){ \
if [ -n "$$(git status --porcelain)" ]; then \
echo " skipped: dirty (commit or stash first)"; \
else git pull --ff-only; fi; \
}; $(eachgist)
## hist -------------------------------------------------------
hist: ## paragraph line counts + cum%% (skip comments)
@cat $(wildcard $(SRC)) | grep -Ev '^[[:space:]]*($(COMMENT))' | \
gawk -f $(KONFIG)/hist.awk
## sh ---------------------------------------------------------
sh: ## tuned bash + vi alias (config from $(KONFIG))
$(call need,nvim,sh)
$(call need,git,sh)
$(call konfig)
@KONFIG=$(abspath $(KONFIG)) APP=$(APP) MAIN=$(MAIN) BANNER=$(abspath $(BANNER)) \
bash --rcfile $(KONFIG)/bashrc -i
## vi ---------------------------------------------------------
F ?= $(firstword $(wildcard $(SRC))) # for vi
vi: ## tuned nvim + catppuccin (config from $(KONFIG))
$(call need,nvim,vi)
$(call konfig)
@NVIM_APPNAME=konfig/nvim nvim --clean -u $(KONFIG)/init.lua $F
## el ---------------------------------------------------------
el: ## tuned emacs (-nw): evil+org-babel+sly+magit, config from $(KONFIG)
$(call need,emacs,el)
$(call konfig)
@emacs -nw -Q --init-directory $(HOME)/.config/konfig/emacs -l $(KONFIG)/init.el $F
## mux --------------------------------------------------------
mux: ## tuned tmux, private socket, config from $(KONFIG)
$(call need,tmux,mux)
$(call konfig)
@KONFIG=$(abspath $(KONFIG)) APP=$(APP) MAIN=$(MAIN) BANNER=$(abspath $(BANNER)) \
tmux -L $(APP) -f $(KONFIG)/tmux.conf new-session -A -s $(APP)
## claude -----------------------------------------------------
claude: ## tmux: left shell | right = claude (top) + `make sh` (bottom)
$(call need,tmux,claude)
$(call konfig)
@T="tmux -L $(APP)"; \
$$T has-session -t $(APP) 2>/dev/null && exec $$T attach -t $(APP); \
$$T -f $(KONFIG)/tmux.conf new-session -d -s $(APP) -c $(CURDIR) \; \
send-keys 'make sh' C-m \; \
split-window -h -c $(CURDIR) \; send-keys 'make sh' C-m 'clear' C-m 'claude' C-m \; \
split-window -v -c $(CURDIR) \; send-keys 'make sh' C-m \; \
select-pane -L; \
exec $$T attach -t $(APP)
## play -------------------------------------------------------
GAMES ?= $(DOOT)/bsdgames-osx # sibling gist of classic bsdgames source
GAME ?= robots # for: make play GAME=<dir>
DEFS ?= # extra -D flags some games need
# strip trailing space left before the # comments above (see L32)
override GAMES := $(strip $(GAMES))
override GAME := $(strip $(GAME))
override DEFS := $(strip $(DEFS))
play: ## build+run a bsdgame: make play GAME=trek (DEFS=-D...)
$(call need,cc,play)
@g=$(GAMES)/$(GAME); \
test -d $$g || { echo "no game '$(GAME)' in $(GAMES)"; \
echo "have: $$(cd $(GAMES) 2>/dev/null && ls -d */ | tr -d /)"; exit 1; }; \
out=$${TMPDIR:-/tmp}/bsdg_$(GAME); err=$${TMPDIR:-/tmp}/bsdg_$(GAME).err; \
echo "building $(GAME)..."; \
cc -w -o $$out $(DEFS) $$g/*.c -lcurses 2>$$err \
|| { echo "build failed:"; cat $$err; exit 1; }; \
exec $$out
robots: ## play ASCII daleks (bsdgames robots)
@$(MAKE) --no-print-directory play GAME=robots DEFS=-DMAX_PER_UID=5
## toys -------------------------------------------------------
# eye-candy needing a brew formula; print install hint if missing
toy = @command -v $(1) >/dev/null || { \
printf "missing: %s (brew install %s)\n" $(1) $(2); exit 1; }; \
exec $(1) $(3)
cmatrix: ## matrix rain (brew install cmatrix)
$(call toy,cmatrix,cmatrix,-ab)
aquarium: ## fish tank (brew install asciiquarium)
$(call toy,asciiquarium,asciiquarium,)
pipes: ## animated pipes (brew install pipes-sh)
$(call toy,pipes.sh,pipes-sh,)
chess: ## play chess (brew install gnuchess)
$(call toy,gnuchess,gnuchess,)
tetris: ## play tetris (brew install vitetris)
$(call toy,vitetris,vitetris,)
checkers: ## play checkers (brew install nbsdgames)
$(call toy,checkers,nbsdgames,)
## pdf --------------------------------------------------------
Font ?= 5 # for ~/tmp/konfig/%.pdf
Cols ?= 3 # for ~/tmp/konfig/%.pdf
Orient ?= landscape # for ~/tmp/konfig/%.pdf
HL ?= heavy # for ~/tmp/konfig/%.pdf a2ps highlight level
BREAK ?= # for ~/tmp/konfig/%.pdf form-feed marker (awk regex; empty=off)
SSH ?= # for ~/tmp/konfig/%.pdf name of exported var w/ a2ps style sheet
~/tmp/konfig/%.pdf : %.$(EXT) ## src -> pdf via a2ps
$(call need,a2ps,pdf)
$(call need,ps2pdf,pdf)
@mkdir -p ~/tmp/konfig
@echo "pdfing : $@ ..."
@D=$$(mktemp -d); trap "rm -rf $$D" EXIT; \
mkdir -p $$D/.a2ps; \
[ -n "$(SSH)" ] && printf '%s\n' "$${$(SSH)}" > $$D/.a2ps/$(LANG).ssh; \
{ [ -n "$(BREAK)" ] && awk '/$(BREAK)/{if(NR>1)printf "\f";next}{print}' $< \
|| cat $<; } | \
HOME=$$D a2ps -Bj --$(Orient) --line-numbers=1 \
--highlight-level=$(HL) --borders=no \
--pro=color --right-footer="" --left-footer="" \
--pretty-print=$(LANG) --footer="page %p." \
-M letter --font-size=$(Font) \
--columns $(Cols) -o - | ps2pdf - $@
@$(OPEN) $@
MDSTYLE ?= tango # pandoc code-token theme for `pretty` (light)
pretty: ## $(f).md -> self-contained html (images embedded) + open. usage: make f=NAME pretty
$(call need,pandoc,pretty)
@test -n "$(f)" || { echo "usage: make f=<basename> pretty"; exit 1; }
@mkdir -p ~/tmp/konfig
@pandoc $(f).md -o ~/tmp/konfig/$(f).html --embed-resources --standalone \
--highlight-style=$(MDSTYLE) --include-in-header=$(KONFIG)/pretty.css
@$(OPEN) ~/tmp/konfig/$(f).html
## death ------------------------------------------------------
# every external dir konfig generates lives under a konfig/ segment of an XDG
# base (+ ~/tmp/konfig). nuke them all; the gist itself is never touched.
DEATHDIRS := ~/.cache/konfig ~/.config/konfig ~/.local/share/konfig \
~/.local/state/konfig ~/tmp/konfig
death: ## wipe ALL generated konfig dirs (cache/config/data/state/tmp)
@echo "These dirs will be DELETED (gist files are safe):"
@for d in $(DEATHDIRS); do echo " $$d"; done
@read -p 'Type DESTROY to confirm: ' a; [ "$$a" = DESTROY ] || { echo aborted; exit 1; }
@read -p 'Really? last chance [y/N]: ' b; [ "$$b" = y ] || { echo aborted; exit 1; }
@rm -rf $(DEATHDIRS)
@echo "gone. re-run any tool to rebuild its dir fresh."
## gists (konfig only) ----------------------------------------
# konfig-only bootstrap of the sibling repos. kept in a SEPARATE file pulled in
# only when make runs inside konfig -- so the targets never reach the shared
# boilerplate siblings include, not even their `make help` (which text-greps
# MAKEFILE_LIST: gists.mk simply isn't on it in a sibling).
ifeq ($(notdir $(CURDIR)),konfig)
-include $(KONFIG)/gists.mk
endif
{
"F5": "command:tree",
"F6": "command:fzf",
"F7": "command:textfilter ruff format -",
"F8": "command:togglecheck",
"Alt-|": "command-edit:textfilter ",
"Alt-v": "VSplit",
"Alt-h": "HSplit",
"Alt-/": "lua:comment.comment",
"CtrlUnderscore": "lua:comment.comment"
}
filetype: lisp
detect:
filename: "(emacs|zile)$|\\.(el|li?sp|scm|ss|rkt|cl|asd)$"
rules:
# special forms / defining forms
- statement: "\\b(defun|defmacro|defvar|defparameter|defconstant|defclass|defmethod|defgeneric|defstruct|defpackage|deftype|defsubst|defcustom|defface|defgroup|define|define-minor-mode|define-derived-mode)\\b"
- statement: "\\b(lambda|let\\*?|flet|labels|macrolet|if|when|unless|cond|case|ecase|typecase|progn|prog1|prog2|block|return|return-from|loop|do\\*?|dolist|dotimes|while|catch|throw|unwind-protect|condition-case|handler-case|ignore-errors|and|or|not|setf|setq|setq-default|push|pop|incf|decf|quote|function|declare|the|values|multiple-value-bind|destructuring-bind|with-open-file|with-temp-buffer|with-slots|save-excursion|require|provide)\\b"
- identifier: "\\b(funcall|apply|mapcar|mapc|mapcan|maphash|reduce|remove-if|remove-if-not|cons|car|cdr|list|append|nth|nthcdr|assoc|member|gethash|format|princ|print|message|error|concat|string=|equal|eq|eql)\\b"
# keyword symbols :foo
- symbol.tag: ":[a-zA-Z][a-zA-Z0-9+*/<>=!?._-]*"
# quoting / reader macros
- special: "#'|'|`|,@?|#\\\\."
# constants
- constant: "\\b([tT]|nil)\\b"
- constant.number: "\\b([0-9]+\\.?[0-9]*|#x[0-9a-fA-F]+|#b[01]+|#o[0-7]+)\\b"
# parens dim
- symbol.brackets: "[()]"
# strings (region so escapes/parens inside don't break)
- constant.string:
start: "\""
end: "\""
skip: "\\\\."
rules: []
# ; line comments (the bundled lisp.yaml omits these entirely)
- comment:
start: ";"
end: "$"
rules:
- todo: "\\b(TODO|FIXME|XXX|NOTE|HACK)\\b"

micro.md — micro, as konfig tunes it

A simple, modern terminal editor with standard keys (Ctrl-s save, Ctrl-c/x/v, Ctrl-z undo). Konfig ships catppuccin-mocha colors, a richer lisp syntax, a python reformatter, and pane splits. Launched isolated — your own ~/.config/micro is untouched.

start it

m file           # alias inside `make sh`

The gist is flat (no dirs) but micro needs a config directory, so make sh builds one at ~/.config/konfig/micro and symlinks the flat repo files into it (settings.json, bindings.json, syntax/lisp.yaml, colorschemes/catppuccin-mocha.micro). Plugins micro downloads land there too — never your real ~/.config/micro. make death wipes it.

alias m="micro -config-dir ~/.config/konfig/micro"

settings you get

colorscheme   catppuccin-mocha (matches bat + banner)
numbers       relative (relativeruler); current line absolute
indent        2 spaces, tabstospaces
keymenu       2-line shortcut bar pinned at the bottom
softwrap      on, scrollbar on

keys (beyond the standard ctrl-set)

F5            toggle file-tree sidebar (filemanager)
F6            fuzzy open / find-in-files (fzf)
F7            reformat selection: python via `ruff format -`
F8            toggle spellcheck (aspell, dict pinned en_US)
Alt-|         pipe selection through a shell cmd (vim's `!`):
              opens `textfilter `, type e.g. `sort -u` or
              `python3 foo.py`, Enter -> output replaces selection
Ctrl-_  Alt-/ comment / uncomment line or selection
Alt-v  Alt-h  split pane vertical / horizontal (Alt = ⌥ Option)
Ctrl-w        cycle between panes
Ctrl-q        close pane (or quit last one)
Ctrl-t        new tab     Ctrl-PgUp/PgDn  switch tabs
Ctrl-a / ^D   select all / duplicate line
Alt-Up/Down   move line up / down

Reformat acts on the selection — Ctrl-a first to format the whole file. The bottom keymenu lists the standard keys; the statusline (right) lists the custom F-keys. Splits open a blank pane; Ctrl-w to it, then open a file. (Alt-keys need "Option as Meta" on in your terminal.)

plugins

Four auto-install on first make sh into the isolated plug dir (never your real ~/.config/micro; make death wipes them):

filemanager   F5 sidebar tree (parity with nvim-tree)
fzf           F6 fuzzy open (needs the fzf binary)
aspell        F8 spellcheck; auto-checks prose + code comments
detectindent  match a file's existing tabs-vs-spaces

Add more: Ctrl-e, then > plugin install <name>; list available with > plugin available.

syntax highlighting

150+ languages ship built-in. Konfig adds a richer lisp.yaml (syntax/lisp.yaml) covering Common Lisp + Emacs Lisp — defun, let, cond, :keywords, #', ; comments — which the bundled one omits. Audition any theme live: Ctrl-e, then set colorscheme <Tab>.

tweaking

Edit the flat repo files (micro.settings.json, micro.bindings.json, micro.lisp.yaml); they are symlinked, so changes are live on the next launch. No per-repo override file — micro config is shared across all gists.

{
"aspell.lang": "en_US",
"colorscheme": "atom-dark",
"keymenu": true,
"relativeruler": true,
"scrollbar": true,
"softwrap": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype)",
"statusformatr": "F5 tree F6 find F7 fmt F8 spell ^_ comment ⌥| pipe",
"tabsize": 2,
"tabstospaces": true
}

newgist.md — spinning up a new gist tool or data repo

Two repo kinds (see style_gist.md): tool gists (code: ezr, nuff, lithp, kah-lua, luamine, luk, repltut) and data gists (CSV only: optimiz=optimize, klassif=classify, textz=text-mining). Both ride konfig's shared Makefile.

part vs whole — a section in a file, or a new gist?

import it  ->  a part   ->  a section in an existing file (nuff.py)
run it     ->  a whole  ->  its own gist

New gist when ANY of: an app/idea with its own CLI + identity (ezr); a different language (lithp=lisp, kah-lua=lua); data (optimiz); non-stdlib deps; wants its own release cadence. Otherwise keep it as a section in the relevant file. Promoting a file -> gist later is cheap; merging scattered gists back is not. Cap a one-file lib at "readable in one sitting" — past that, split by theme or promote.

recipe (the order that works)

  1. BUILD in ~/gists/<name>/: .py (+ test_.py) konfig-style code (style_code.md) ,.md man-page README (style_gist.md) Makefile knobs + include $(KONFIG)/Makefile pyproject.toml if pip-installable banner.txt figlet name + tagline Verify: make test. Data comes from sibling gists (../optimiz, ../klassif, ../textz) — never bundled in a tool gist.

  2. CREATE the gist (it must exist before any git push). Use the GitHub API with the keychain token; POST /gists with the files: tok=$(printf 'protocol=https\nhost=github.com\n\n'
    | git credential fill | awk -F= '/^password=/{print $2}') curl -s -X POST -H "Authorization: token $tok"
    -H "Accept: application/vnd.github+json"
    https://api.github.com/gists -d @payload.json payload = {"description":..,"public":true,"files":{name:{content}}}. BIG files (CSVs) go via git push afterward — the API caps payload.

  3. CLONE it back so the dir is git-backed, then point origin at the shortener (you make tiny.cc/ -> the gist html_url by hand): cd ~/gists && rm -rf && git clone git -C remote set-url origin http://tiny.cc/ If make push ever no-ops on a fresh gist (push can't traverse the tiny.cc redirect), give origin a real PUSH url while keeping tiny.cc for fetch: git remote set-url --push origin <gist-git-url>.

  4. WIRE konfig: in gists.mk add a <name>: target (calls $(sync)) and put <name> on the gists: line. Then make <name> clones or pulls it on a fresh box; make gists does the whole constellation.

  5. WIRE fyi (the website Tools page): fyi/Makefile add <name>:<dir>:,<name>.md to TOOLS fyi/etc/tools.txt one blurb line <name>|short description fyi/CLAUDE.md a row in the curated list then make tools (pandoc) + push. Page lands at timm.fyi/tool-.html. If the gist ALSO ships a long-form <name>.md "tour" (genetic-stanza tutorial), that routes to a BLOG post, not Tools — render it through fyi's etc/blog.html template and copy its images into fyi assets/img/. See fyi/CLAUDE.md "Tools vs Blog".

  6. PYPI (optional, code gists): make push2pypi. The FIRST upload of a NEW project needs an ACCOUNT-scoped token (project-scoped tokens cannot create the project -> 403). After it exists, scope a token to that project and store it in ~/.pypirc. NEVER paste a token into a chat or log; if one leaks, revoke it immediately.

where a name lives (the rename/retire checklist)

A gist name <x> is repeated in many places. Renaming or retiring means touching ALL of them — grep first: grep -rn '<x>' ~/gists ~/gits/timm/fyi.

inside ~/gists/<x>/        <x>.py test_<x>.py ,<x>.md (header,
                           imports, API, badges, "Files:" links),
                           Makefile (APP MAIN LINT + test target),
                           pyproject.toml (name readme py-modules
                           Homepage url), banner.txt (figlet + url)
the gist itself            file names (git mv), description (API
                           PATCH /gists/<id>), origin remote url
konfig/gists.mk            the `<x>:` target + the `gists:` line
konfig/*.md                style_code.md, newgist.md (the tool list
                           + any "canonical lib" mention)
fyi/Makefile               TOOLS entry `<x>:<x>:,<x>.md`
fyi/etc/tools.txt          the `<x>|blurb` line
fyi/CLAUDE.md              the curated-list row
fyi pages                  rebuild (`make tools`) -> tool-<x>.html;
                           git rm the stale old page
PyPI                       (code gists) no rename API — upload the
                           new name, delete old project by hand
                           (web UI only); bump version if needed
you, by hand               tiny.cc/<x> redirect; revoke any token

renaming or retiring a gist

Same list, run as a sweep:

  1. grep -rn '<old>' ~/gists ~/gits/timm/fyi — the worklist.
  2. Rename in-repo: git mv files, edit contents, make test.
  3. Push the renamed files; PATCH the gist description; repoint origin.
  4. konfig + fyi edits above; make tools; commit konfig, fyi, gist.
  5. PyPI: upload new name, hand-delete old project; repoint tiny.cc.
  6. Retiring (not renaming): drop the gists.mk target + gists: entry, the fyi TOOLS/tools.txt/CLAUDE.md rows, git rm the fyi page; delete the gist + PyPI project by hand.

conventions that bit us

  • Gists are FLAT — no subdirectories. Multi-dir tools don't fit: flatten, or generate dirs at runtime under ~/.cache/konfig/... (see konfig isolation; make death wipes all generated dirs).
  • Data lives in sibling DATA gists, never inside a tool gist. Refer to it as ../optimiz/foo.csv — the sole sanctioned relative path.
  • One data gist per task family: optimiz (optimize), klassif (classify), textz (text-mining). !-suffixed CSV header = klass.
  • Always run python -B (no pycache); .gitignore __pycache__/, *.pyc, dist/, build/, *.egg-info/.

nvim.md — neovim, as konfig tunes it

Konfig's init.lua is a minimal nvim 0.12 config: catppuccin-mocha colors + nvim-tree file sidebar. Launched isolated — your own nvim config is untouched.

start it

make vi          # opens first source file
make vi F=foo.py # open a specific file
vi file          # alias inside `make sh`

Under the hood: NVIM_APPNAME=konfig/nvim nvim --clean -u $(KONFIG)/init.lua. --clean ignores your ~/.config/nvim; NVIM_APPNAME=konfig/nvim buries all state under ~/.{config,local/share,local/state,cache}/konfig/nvim — never your real nvim dirs. make death wipes the lot.

First run downloads three plugins via vim.pack (nvim >= 0.12): catppuccin, nvim-web-devicons, nvim-tree.

settings you get

leader        space
numbers       on, cursorline on
indent        2 spaces, expandtab
search        ignorecase + smartcase (caps = exact)
clipboard     unnamedplus (yank = system clipboard)

file sidebar (nvim-tree)

SPACE e       toggle 25%-width file tree
ENTER         open file       a  add file
d             delete          r  rename
R             refresh

netrw is disabled; nvim-tree replaces it.

survival vi (if rusty)

i / ESC       insert / back to normal
:w :q :wq     save / quit / both
u  ctrl-r     undo / redo
/text n N     search, next, prev
dd yy p       cut line, copy line, paste
gg G :42      top, bottom, line 42

per-repo overrides

Drop init.local.lua in the repo root; it is loaded last via pcall (silent if missing), so it wins:

-- init.local.lua
vim.o.wrap = false
vim.filetype.add({ extension = { mal = "lisp" } })

Never fork konfig's init.lua for one repo.

<style>
html { font-size: 16px; }
body {
max-width: 46rem; margin: 2.2rem auto; padding: 0 1.2rem;
font: 1rem/1.62 -apple-system, "Helvetica Neue", Arial, sans-serif;
color: #232823; background: #fcfdfc;
}
h1, h2, h3 { line-height: 1.2; color: #1f3a1f; }
a { color: #2e6f3e; }
hr { border: none; border-top: 1px solid #cdd8c9; margin: 2.2rem 0; }
img.banner { display: block; margin-bottom: 1.6rem; } /* column width */
/* code blocks: light greenish-gray, padded, rounded */
pre {
background: #edf2ec;
border: 1px solid #d6e0d2;
border-radius: 8px;
padding: 0.95rem 1.15rem;
overflow-x: auto;
line-height: 1.45;
font-size: 0.92rem;
}
code { font-family: "SF Mono", Menlo, Consolas, monospace; }
:not(pre) > code { /* inline code */
background: #eef3ed; border-radius: 4px;
padding: 0.08em 0.35em; font-size: 0.9em;
}
img { max-width: 100%; }
table { border-collapse: collapse; }
td, th { padding: 0.3rem 0.6rem; border: 1px solid #dde4da; }
blockquote { color: #555; border-left: 3px solid #cdd8c9; padding-left: 1rem; }
</style>

style_code.md — Python style for these gists

Goal: small, terse, readable in one sitting. ~300–400 lines of real work. No deps. Pure stdlib. Pedagogical: every line earns its place.

principles (language-neutral)

Shared across every gist, any language:

  • COI compose, don't inherit

  • LoB locality of behavior: a type's methods + the funcs on it live together (wrapper next to its algorithm). Exception: cross-type op-families may cluster (all ctors, all adds).

  • SSOT config from one help/doc string

  • BOB funcs <=5 lines — warn, not mandatory

  • BAIL one-line guards

  • KISS one func, one job

  • R3 rule of three: don't wrap until 3rd real use (egs don't count); 1–2 uses = inline

  • DSL when a shape repeats, invent tiny notation kept as data (string/table) + write ONE small interpreter. See below.

  • ZIP max logic/screen, min whitespace

  • HINT names = types (see ## names)

  • ASCII only in source — no unicode arrows/glyphs; they break the a2ps PDF (make pdf)

  • PATHS no naked cross-repo refs. A gist never hardcodes another repo's location (no ../sibling literals, no absolute /Users/, /home/). Resolve via $DOOT/$KONFIG env, else find_up; the sole exception is the KONFIG ?= ../konfig bootstrap default. See style_gist.md "cross-repo references". Audit:

    grep -rnE '\.\./[a-z]|/Users/|/home/|/gits/' *.$(EXT) Makefile
    

    every hit must route through the root var, not a literal.

DSLs (notation as data + one interpreter)

Recurring shapes encoded as data, each read by one small engine. Spot repetition → minimal syntax → single interpreter. Adding a row/col/test/flag becomes new data, not new code.

notation example
CSV header = schema Clndrs Lbs- Mpg+ klass!
per-file help string = help + config ## options / ## egs blocks
test cases = triples {"mid", got, want}
Makefile self-doc target: ## desc, Var ?= v # for X

data records: CSV

Header row names cols. Caps first letter = numeric col, else symbolic. Suffix: X=skip, -=goal min, +=goal max, !=klass. Rows sorted ascending by distance-to-heaven (lower = better).

Clndrs, Volume, HpX, Lbs-, Acc+, Mpg+
4,      90,     48,  1985, 21.5, 40

on "update"

When asked to "update" code to current style:

  1. Re-read the style doc(s)
  2. Audit every file in the gist
  3. Print a plan (file → what changes)
  4. Pause for confirmation
  5. On approval: apply

layout

#!/usr/bin/env python3 -B
"""
name.py, one-line summary (library / cli)
(c) YEAR, Author <email>, MIT license

2–4 line narrative: what it does, key idea, dispatch trick.

Options:
 -x --xname meaning   xname=default
 ...

eg: python3 name.py -f FILE --tree
"""
import sys, re
from math import ...
from random import ...

BIG  = inf
TINY = 1e-30

# ## constructors -----------------------------------------------
def Foo(...): return o(it=Foo, ...)

# ## one-polymorphic-op (dispatch on .it) -----------------------
def add(i, v): ...

# ## methods ----------------------------------------------------
...

# ## lib --------------------------------------------------------
class o(dict): ...
def csv(file): ...
def cli(the, doc, egs={}): ...

# ## egs (run via --name) ---------------------------------------
def test_the(): ...
def test_tree(): ...

the = settings(__doc__)
if __name__ == "__main__":
  cli(the, __doc__, globals())

conventions

indent           2 spaces
line width       ~70 chars where possible
type hints       none
docstrings       module only; not per-function
comments         section dividers `# ## name ----`, plus
                 end-of-line for non-obvious lines only
constants        UPPERCASE (BIG, TINY)
private fields   leading _ (repr skips them)

data records: o(dict)

class o(dict):
  __getattr__ = dict.get
  __setattr__ = dict.__setitem__

Use o(k=v,...) for plain records. Attr + dict access both work. No dataclass overhead.

tagged unions

Instead of subclasses, every record carries it=<Constructor>. Polymorphic functions dispatch on i.it is Foo. One add(), one mid(), one spread() handle Num/Sym/Data uniformly.

def add(i, v):
  if   i.it is Data: ...
  elif i.it is Sym:  ...
  else:              ...   # Num

tiny helpers

Lambdas inline when one-shot:

yfun = yfun or (lambda r: r[root.cols.klass.at])
ymid = lambda rs: sum(disty(root, r) for r in rs)/max(1, len(rs))

Walrus for one-pass init+use:

out += [col := (Num if s[0].isupper() else Sym)(s, at)]

names

Function arguments mirror their class. def foo(data, rows, col) tells the reader the types without annotations.

arg name   means
--------   --------------------------------------------------
data       a Data
rows       list of rows (each row = list of cell values)
row        one row
col        a Sym or Num. **collective noun** for either
cols       list of cols
num        a Num specifically (when Sym wouldn't fit)
sym        a Sym specifically
t          a Tree node (interior) or Data (leaf)
the        global config (settings(__doc__))

One-letter names allowed only for tight loops / lambdas where the larger name is in scope:

for c in data.cols.x:        # c shadows nothing; col=c implied
  for v in row:              # v=value, r-row already iterated
    ...

Avoid d for data, r for row, c for col at function-argument scope — that's where the type cue matters most. Use the long name.

yes:  def disty(data, row):  ...
no:   def disty(d, r):       ...

Full words for module-level / exported names (Data, treeShow, generalized).

Short scalars, any language, tight scope only:

n        int / count / index
s, v     string / scalar value
at       column index
lo, hi   bounds
mu, sd   mean, stdev
plurals  cols rows xs(=features) ys(=goals) = lists

(t and i are language-specific: t = table/tree, i = loop index or method receiver. Disambiguate per language doc.)

CLI

Shared protocol (any language in these gists):

  • The help/doc string is the SSOT for options + defaults.
  • Option line --name=default seeds config; action line (no =) doesn't.
  • Flags are long-only except canonical shorts -t(train), -T(test), -h(help). Unknown flag = error.
  • --name runs the matching test/eg.
  • Every file is standalone-runnable AND importable (CLI or library).

Python: options live in the module docstring; settings(doc) parses name=default pairs into the. cli(the, doc, globals()) toggles booleans, sets values, runs test_<name>() for any --<name>.

Every test is def test_X(): — discoverable by grep '^def test_'.

library vs app config (no global the in libraries)

An app (ezr) owns a global the = settings(__doc__). A library others import (nuff, on PyPI) must NOT — a global config is a landmine in someone else's program. So library functions carry their tuning as keyword args, with the default living once at the lowest function:

def disty(data, row, **kw): minkowski(..., **kw)   # p flows through
def minkowski(vals, p=2): ...                       # default lives here
def same(xs, ys, cliff=0.195, conf=1.36): ...
def shuffle(lst, rng=random): ...                   # pass your own RNG

settings(s) still parses var=val from a string into an o, but the caller (the app) owns the result; the library never reaches for it. nuff.py is the canonical tiny stdlib-only library (records, io, rand, stats, columns, distance, bayes, tree — one file, zero deps).

numerics

BIG  = inf            for "no cut yet" sentinel
TINY = 1e-30          for divide-by-zero guards in z-score / disty
# NEVER use 1/BIG as epsilon — inf division underflows to 0

tests run by seed

cli calls seed(the.seed) before each test fn — runs are reproducible.

avoid

type hints (noise for ~300-line scripts)
abc / dataclass / pydantic
multi-paragraph docstrings on functions
try/except for control flow (except `of()` int/float coercion)
f-strings when % does the job in a one-liner
classes (except `o`); use closures or modules of functions

style_gist.md — gist structure + README style

Goal: every repo readable on gist.github.com preview, runnable in three commands, and skinnable like a unix man page.

repo split

Three sibling repos in ~/gists/:

konfig/       shared boilerplate (Makefile, bashrc, hist.awk,
              banner.sh, init.lua, tmux.conf)
optimiz/      example datasets (CSV only, no code)
<project>/   code + project-specific README + Makefile knobs

Each project repo includes konfig's Makefile, points at optimiz for data. No code/data/config mixed in one repo.

repo file layout

<project>/
  ,project.md      README; comma-prefix sorts to top of gist listing
  Makefile         knobs (APP/MAIN/EXT/...) then include konfig
  project.py       single-file source (see style_code.md)
  banner.txt       ascii art shown by `make help` / `make sh`

Makefile pattern

MANDATORY: every gist — code, data-only, or doc-only — carries a Makefile. Knobs first, then a loud-failure guard, then include $(KONFIG)/Makefile as the last line. That include is what gives every repo the shared targets (help doctor check push hist sh vi mux pdf) for free.

KONFIG ?= ../konfig
APP   := project
MAIN  := project.py
EXT   := py
LANG  := python
LINT  := ruff check *.py
TOOLS := python3:run-py ruff:lint
PKG   := python3 gawk ruff neovim tmux

$(KONFIG)/Makefile:
	@test -f $@ || { echo "missing konfig: ..."; exit 1; }
include $(KONFIG)/Makefile

Data-only and doc-only repos (no source): omit MAIN/EXT/LANG/LINT/TOOLS. Keep KONFIG, APP, PKG, the guard, and the include — minimum viable Makefile:

KONFIG ?= ../konfig
APP    := project
PKG    := gawk neovim tmux

$(KONFIG)/Makefile:
	@test -f $@ || { echo "missing konfig: git clone http://tiny.cc/konfig $(KONFIG)"; exit 1; }
include $(KONFIG)/Makefile

cross-repo references (no naked paths)

A gist NEVER hardcodes another repo's location. Naked ../sibling literals and absolute /Users/... (or /home/...) paths are VERBOTEN: they break the moment a gist moves (into old/, a deeper dir) or a sibling relocates.

All external references resolve through ONE overridable root, tried in order:

1. env var      $DOOT (gists root), $KONFIG=$DOOT/konfig
                exported once in konfig/bashrc            -- wins
2. upward search climb parents for a dir named `konfig`   -- fallback
3. relative     `?= ../konfig`           -- last-resort default only

$DOOT = the dir holding all the sibling gists (konfig, optimiz, ...). One knob; move the tree or nest a gist, edit one line, every gist follows.

# Makefile: `?=` lets an exported env value override the default
KONFIG ?= ../konfig
DOOT   ?= $(abspath $(KONFIG)/..)
DATA   ?= $(DOOT)/optimiz/auto93.csv      # never bare ../optimiz

# Python: env first, then search, then the documented default
DOOT = os.environ.get("DOOT") or find_up("konfig", then="..")
DATA = f"{DOOT}/optimiz/auto93.csv"

Sole sanctioned naked path: the KONFIG ?= ../konfig bootstrap default (and DOOT derived from it). Everything else hangs off the root var. Doc/README example commands may show ../optimiz for brevity; runnable code and Makefile recipes may not.

Audit (see style_code.md PATHS): one grep surfaces every offender.

local Makefile rules (after include)

Konfig's Makefile owns generic targets (help, doctor, check, push, hist, sh, vi, mux, pdf). Repo-specific targets go in the local Makefile after include $(KONFIG)/Makefile.

Every repo should define a local test rule that runs all self-checks — language/framework-specific, so it can't live in konfig.

Konfig's check = static lint. Local test = runtime behaviour. Both kept separate so CI can call them independently.

one rule per test, UPPERCASE-discovered

Each test is its own rule with an UPPERCASE name (TREES, MAIN, PARSE, EQUIV, ...). The local test rule auto-discovers them by grepping the Makefile — no central list to keep in sync. Adding a new test = adding a new UPPERCASE rule.

BANG:  ## test: end! appends correctly
	@sbcl --script tests/bang.lisp

PARSE: ## test: csv parser round-trips
	@sbcl --script tests/parse.lisp

test:  ## run every UPPERCASE rule
	@gawk -F: '/^[A-Z][A-Z_]*:[^=]/ {print $$1}' $(MAKEFILE_LIST) | \
	  sort -u | while read t; do \
	    printf "\n=== %s ===\n" "$$t"; $(MAKE) -s $$t; done

Why UPPERCASE: rule name = discovery marker (no comment to drift out of sync); visually clusters in make help; can't collide with normal lowercase targets or VAR := assignments (regex excludes := via [^=]). Language-agnostic — works for pytest, sbcl, shell, anything callable from make.

local rc / init overrides

Konfig's bashrc, init.lua, tmux.conf each source an optional sibling-of-CWD file at the end so repo-specific tweaks layer on without forking konfig:

bashrc:    [ -f "$PWD/bashrc.local" ]    && source "$PWD/bashrc.local"
init.lua:  pcall(dofile, vim.fn.getcwd() .. "/init.local.lua")
tmux.conf: source-file -q tmux.local.conf

Silent if missing. Local file sources last → wins on conflict. Use for per-project aliases (e.g. alias r='sbcl --script ...'), language-specific keymaps, project-specific tmux panes.

README (,project.md)

first 7 lines = gist preview hook

Gist listing renders top of file. Pack the hook tight:

line 1   <!-- copyright comment -->                (invisible)
line 2   right-aligned badges (single line!)       (visible)
line 3   blank
line 4   ### [http://tiny.cc/project](http://tiny.cc/project)
line 5   one-paragraph pitch with concrete facts
line 6   blank
line 7   ```bash + `# install and test` + clone line

badges

<p align="right"> is stripped by gist. Use one <img align="right"> per badge, all on a single line (no newlines between — newlines trigger implicit paragraph breaks that kill the float chain). Order in source is reversed: first floats furthest right.

Quote the attribute (align="right", not align=right).

Standard four: Purpose · License · Language · Author.

title

### [http://tiny.cc/project](http://tiny.cc/project)

H3, clickable tiny.cc URL. Skips repeating "Project — " noise; the URL is the identifier.

TOC bars (above NAME)

Two one-line pipe-separated TOCs sit between the install snippet and ## NAME. Standard in every gist README:

**Sections:** [NAME](#name) | [SYNOPSIS](#synopsis) | ... | [LICENSE](#license)

**Files:** [foo.py](http://tiny.cc/<gist>#file-foo-py) | [bar.md](http://tiny.cc/<gist>#file-bar-md) | ...
  • Sections — section anchors within this README. Labels in SHORT-CAPS, match the H2 headers. Skip sections that don't exist in the project. These stay RELATIVE (#name, #synopsis): they jump within the same README, so never put tiny.cc here. (Contrast Files, which ARE absolute.)
  • Files — sibling files via the ABSOLUTE http://tiny.cc/<gist>#file-<name>-<ext> (so the links resolve even when the README is read off the gist page). Anchor rule: lowercase, dots/punctuation collapse into a single -, never double. E.g. lib-.lisp#file-lib-lisp (NOT lib--lisp); fft-2small.lisp#file-fft-2small-lisp. Omit the README itself and trivial files (banner.txt, .gitignore). Order by importance, not alphabetical.

Keeps gist preview navigable without scrolling.

body: man-page hybrid

H2 section headers in markdown. Bodies indented 4 spaces so they render as <pre>. Looks like man(1) output. Tables become aligned text columns (no markdown table noise on mobile).

standard sections (in order)

NAME          one-line summary
SYNOPSIS      usage line
OPTIONS       flag table
DATA          input format (CSV column suffix conventions)
TESTS         --test_X discoverable entry points
OUTPUT        what a typical run prints
EXIT          exit codes
SEE ALSO      sibling repos with tiny.cc URLs
LICENSE       MIT + choosealicense.com link
AUTHOR        name + email

DATA after OPTIONS — matches unix man ordering (FILES after OPTIONS).

CSV column-name protocol (DATA convention)

first char UPPER  -> numeric (Num)
first char lower  -> symbolic (Sym)
suffix '+'        -> numeric goal, maximize
suffix '-'        -> numeric goal, minimize
suffix '!'        -> symbolic goal (klass)
suffix 'X'        -> ignore
else              -> predictor
missing value     -> '?'

Self-describing header; no separate schema file. Same protocol across every project + every CSV in optimiz/.

files TOC anchors

Link sibling files in the README TOC with ABSOLUTE gist URLs: http://tiny.cc/<gist>#file-<name>-<ext> (lowercase, dots/punctuation collapse into single -, never double). E.g. luamine.luahttp://tiny.cc/luamine#file-luamine-lua; lib-.lisp…#file-lib-lisp (NOT lib--lisp).

URLs

tiny.cc/<reponame> redirects to the gist. Use in:

  • README title
  • SEE ALSO cross-links
  • install snippet (git clone http://tiny.cc/foo)

license

MIT. Badge link → https://choosealicense.com/licenses/mit/. LICENSE section repeats the link.

set -s escape-time 0
set -g mouse on
set -g default-terminal "tmux-256color"
set -as terminal-features ",*:RGB" # truecolor passthrough (catppuccin in micro/nvim)
set -g default-command 'bash -l'
set -g prefix C-Space
unbind C-b
bind C-Space send-prefix
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
setw -g mode-keys vi
bind-key -T copy-mode-vi v send -X begin-selection
bind-key -T copy-mode-vi y send -X copy-pipe-and-cancel 'pbcopy'
# mouse drag -> copy to macOS clipboard, no auto-jump to bottom
bind-key -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-and-cancel 'pbcopy'
# sessions: C make new, S pick
bind C command-prompt -p "session:" "new-session -s '%%'"
bind S choose-tree -Zs
# status line at top, one muted accent (steel blue) so it reads but doesn't shout
set -g status-position top
set -g status-style "bg=default,fg=colour245"
set -g window-status-current-style "fg=colour110,bold"
set -g window-status-style "fg=colour245"
# per-repo overrides (sourced last so they win). silent if missing.
source-file -q tmux.local.conf
# # [Core Engine & Performance: 6 Commands]
# set -s escape-time 0
# set -g mouse on
# set -g base-index 1
# setw -g pane-base-index 1
# set -g default-terminal "tmux-256color"
# set -as terminal-features ",xterm-256color:RGB"
#
# # [Prefix Config: 3 Commands]
# set -g prefix C-a
# unbind C-b
# bind C-a send-prefix
#
# # [Intelligent Workspace Behavior: 3 Commands]
# set -g history-limit 50000
# set -g renumber-windows on
# setw -g automatic-rename on
#
# # [Visual Notification Tuning: 3 Commands]
# setw -g monitor-activity on
# set -g visual-activity off
# set -g focus-events on
#
# # [Window Splitting & Paths: 2 Commands]
# bind | split-window -h -c "#{pane_current_path}"
# bind - split-window -v -c "#{pane_current_path}"
#
# # [Vim Home-Row Navigation: 4 Commands]
# bind h select-pane -L
# bind j select-pane -D
# bind k select-pane -U
# bind l select-pane -R
#
# # [Vim Pane Resizing: 4 Commands]
# bind -r H resize-pane -L 5
# bind -r J resize-pane -D 5
# bind -r K resize-pane -U 5
# bind -r L resize-pane -R 5
#
# # [Vim-Style Text Copying: 3 Commands]
# setw -g mode-keys vi
# bind-key -T copy-mode-vi v send -X begin-selection
# bind-key -T copy-mode-vi y send -X copy-selection-and-cancel
#
# # [Minimalist Dark Status Bar: 4 Commands]
# set -g status-style "bg=#1e1e2e,fg=#cdd6f4"
# set -g status-left "#[fg=#b4befe,bold] #S "
# set -g window-status-current-format "#[fg=#a6e3a1,bold] [#I:#W] "
# set -g window-status-format "#[fg=#6c7086] #I:#W "
# ## tuned tmux. run on private socket: tmux -L <name> -f tmux.conf
# ## --- core ---
# #set -g default-terminal "tmux-256color"
# #set -ag terminal-overrides ",xterm-256color:RGB,*:RGB"
# #set -g mouse on
# #set -g base-index 1
# #setw -g pane-base-index 1
# #set -g renumber-windows on
# #set -g escape-time 0
# #set -g history-limit 50000
# #set -g focus-events on
# #setw -g aggressive-resize on
# #setw -g mode-keys vi
# #
# ## --- prefix: C-Space ---
# #unbind C-b
# #set -g prefix C-Space
# #bind C-Space send-prefix
# #
# ## --- splits keep cwd; | vertical, - horizontal ---
# #unbind '"'
# #unbind %
# #bind '|' split-window -h -c "#{pane_current_path}"
# #bind '-' split-window -v -c "#{pane_current_path}"
# #bind '_' split-window -v -c "#{pane_current_path}"
# #bind c new-window -c "#{pane_current_path}"
# #
# ## --- sessions: fast make/jump ---
# #bind C command-prompt -p "session:" "new-session -s '%%'"
# #bind S choose-tree -Zs
# #bind -n M-Left switch-client -p
# #bind -n M-Right switch-client -n
# #
# ## --- reload (sources current active config file) ---
# #bind r source-file "#{config_files}" \; display "reloaded #{config_files}"
# #
# ## --- copy-mode vi ---
# #bind -T copy-mode-vi v send -X begin-selection
# #bind -T copy-mode-vi y send -X copy-selection-and-cancel
# #
# ## --- catppuccin-mocha status bar ---
# #set -g status-position top
# #set -g status-justify centre
# #set -g status-style "bg=#1e1e2e,fg=#cdd6f4"
# #set -g window-status-style "bg=#1e1e2e,fg=#a6adc8"
# #set -g window-status-current-style "bg=#cba6f7,fg=#1e1e2e,bold"
# #set -g window-status-format " #I:#W "
# #set -g window-status-current-format " #I:#W "
# #set -g pane-border-style "fg=#45475a"
# #set -g pane-active-border-style "fg=#89b4fa"
# #set -g message-style "bg=#cba6f7,fg=#1e1e2e,bold"
# #set -g status-left-length 40
# #set -g status-right-length 60
# #set -g status-left "#[bg=#89b4fa,fg=#1e1e2e,bold] #S #[bg=#1e1e2e,fg=#89b4fa]"
# #set -g status-right "#[fg=#a6e3a1]#[bg=#a6e3a1,fg=#1e1e2e,bold] %H:%M #[bg=#a6e3a1,fg=#fab387]#[bg=#fab387,fg=#1e1e2e,bold] %d-%b "

tmux.md — tmux, as konfig tunes it

Konfig's tmux.conf is small: C-Space prefix, mouse on, vi copy mode, quiet status line at top. Each repo runs on a private socket, so its tmux can't collide with any other session.

start it

make mux         # tmux -L $(APP) -f $(KONFIG)/tmux.conf, session $(APP)
make claude      # 3-pane layout: shell | claude + shell

Re-running make mux re-attaches (new-session -A). Detach with prefix d; the session keeps running.

prefix = C-Space

Hold ctrl, tap space, release, then the key:

C-Space |       split vertical   (panes side by side)
C-Space -       split horizontal (panes stacked)
C-Space d       detach
C-Space C       new session (prompts for name)
C-Space S       pick session from a tree
C-Space z       zoom/unzoom current pane
C-Space x       kill pane
C-Space o       next pane (or just click — mouse is on)

Splits open in the current pane's directory.

copy mode (vi keys)

C-Space [       enter copy mode
h j k l         move        /text  search
v               start selection
y               yank -> macOS clipboard (pbcopy), exits copy mode

Mouse drag also copies straight to the clipboard, without jumping the view to the bottom.

status line

Top of screen, muted grey; current window in steel blue bold. No clocks, no powerline noise.

per-repo overrides

Put tmux.local.conf next to the repo Makefile; sourced last (silently skipped if missing), so it wins:

# tmux.local.conf
bind r source-file "#{config_files}" \; display "reloaded"

sockets, in case of confusion

-L name = separate server per repo. tmux ls shows nothing? You're asking the default server. Use:

tmux -L luamine ls

konfig todo

done

  • BANNER env fossil leak — fixed 2026-06-04.
    • Makefile: ifeq ($(origin VAR),environment) guard on APP, MAIN, BANNER. Env-sourced values get reset to defaults; repo Makefile assignments (origin=file) still win. KONFIG left alone — users may legitimately export it.
    • bashrc: unset BANNER APP MAIN KONFIG after aliases + banner consumed, so child shells / tmux / claude can't inherit.

bugs

(none open)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment