diff options
| author | Igor Soarez <igor@soarez.org> | 2026-09-17 19:45:23 +0100 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-09-17 19:45:23 +0100 |
| commit | 13cc065ec014b86369dfe4ad60d1a43fa06fd251 (patch) | |
| tree | 684690c61b7b59758c2aac132c65b81c86de0608 /CLAUDE.md | |
dotfiles
Shell, editor and terminal configuration, the tools that go with them, and
szecret, an age-based secret manager whose store lives elsewhere.
History restarts here. What came before was not written to be read by
anyone else, and carried machine names, addresses and an encrypted secret
store; it is kept privately instead. What is left is meant to be safe on
any machine, including one whose owner is not me: see CLAUDE.md for the
rule it is written to.
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a78bc50 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,230 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is a personal dotfiles repository containing configuration files and utility scripts for a Unix/macOS development environment. The repository is organized into several key areas: + +- **config/**: Shell configurations (zsh, profile), editor configs (nvim, ideavim), terminal configs (alacritty, tmux), and application configs (git, bat, lsd, hammerspoon) +- **bin/**: Custom utility scripts and tools +- **secrets/**: `szecret`, an age-based secret manager. The tool only; its store lives outside this repo + +## What may live here, and what may not + +This repo is the account layer, and it is used on machines that are not trusted +with anything else: it must be safe for the owner of such a machine to read +every line of it. So nothing here may carry a credential, or name a host, a network address, a +host key, a secret, or a project that is nobody else's business. Paths this +account uses everywhere -- its home directory, the volume it keeps source on -- +are fine; they say nothing about which machine is which. Anything that would +fail that test belongs in a private layer instead, and reaches a machine +through the hooks below. + +It must also keep working on a machine that has only this repo, with no private +layer present and none of the hooks filled. + +Where a private layer is installed, it points `core.hooksPath` at a check of +its own, so a commit or push from that machine fails if anything on its list +turns up here. A machine without it has no list -- and nothing to leak. + +A private layer -- on the owner's own machines, the `cloud-setup` repo at +`~/src/soarez/cloud-setup`, whose `link.sh` this repo's `link.sh` runs -- adds +what it needs through these, each of them optional and absent by default: + +| Hook | Filled with | +|---|---| +| `~/.config/shell/env.d/*.sh` | environment for every shell, sourced by `config/env` | +| `~/.config/shell/profile.d/*.sh` | interactive-shell setup, sourced by `config/profile` | +| `~/.config/ssh/config-fleet` | ssh host blocks, included first by `config/ssh/entry` | +| `~/.config/git/config-fleet` | git settings, included by `config/git/config` | +| `~/.config/claude/mcp-servers.d/*.json` | MCP servers, registered by `link/claude.sh` | +| `~/.claude/rules/*.md` | extra Claude Code user memory, loaded next to `config/claude/CLAUDE.md` | +| `~/.config/pi/settings.d/*.json` | pi settings merged into the generated `~/.pi/agent/settings.json` by `link/pi.sh` | +| `~/.config/agent-skills.d/*` | agent skills, linked into every installed agent by `link/skills.sh` | +| `~/.local/share/szecret/{store,id.pub}` | the szecret store; the tool is here, its secrets are not | +| `~/.config/shell/dir-hashes.zsh` | more zsh named directories, sourced by `config/zshrc` | + +`~/.localenv` (every shell) and `~/.localprofile` (interactive) stay what they +always were: per-host files written by hand, in no repo at all. + +## Automatic syncing + +On some machines a timer keeps this clone up to date: it rebases onto +`origin/master` and, when something arrived, runs `./link.sh yes`. That is not +part of this repo -- `dotfiles-sync-enable`, `dotfiles-sync-disable` and +`dotfiles-sync` come from the private layer, and a machine without it syncs only +when someone pulls. + +Where it is running, `HEAD` can move under you between two commands, so run +`dotfiles-sync-disable` before anything that needs a stable tree (`stash`, +`rebase`, `bisect`, checking another ref out) and `dotfiles-sync-enable` +afterwards. It never commits or pushes on your behalf: your edits sit in the +working tree until you commit and push. + +`bin/dotfiles-sync` here is a transitional shim that hands over to the private +layer's copy, for machines whose timer still names this path. It can go once +none do. + +## Common Commands + +### Setting up a machine +```bash +./link.sh yes # link the config into place (see below) +./install.sh # then the tools, checkouts and nvim plugins it expects +./macos-defaults.sh # new Mac only, once: baseline macOS settings; resets the Dock +``` + +`install.sh` is safe to re-run -- each step only adds what is missing -- and +`Brewfile` is its package list: the account's tools, plus every Nerd Font +Homebrew has. A private layer's bootstrap may call both scripts for you. + +### Setup and Linking +```bash +# Preview what will be linked (dry run) +./link.sh + +# Actually link all dotfiles +./link.sh yes +``` + +### Secret Management +```bash +# List available secrets +szecret list + +# Get a secret value +szecret get <secret-name> + +# Store a new secret +szecret put <secret-name> +``` + +### Theme Management +```bash +# Show current theme (mode and colors) +theme + +# Show current mode only +theme --mode + +# Show current colors only +theme --colors + +# Set theme +theme --set [light|dark] [colorscheme] + +# List available colorschemes +theme --list +theme --list-alacritty +theme --list-tmux +theme --list-neovim +theme --list-bat +``` + +## Architecture & Organization + +### Configuration Management +The repository uses a centralized linking system where `link.sh` creates symlinks from `config/` files to their expected locations in `$HOME` and `$XDG_CONFIG_HOME`. This allows version control of dotfiles while maintaining standard file locations. + +`link.sh` holds only the plain one-line links. Anything with logic of its own is a part in `link/` (`ssh`, `claude`, `pi`, `skills`, `icloud`, `hammerspoon`, `kwin`): a script with the same `[yes]` dry-run interface that decides for itself whether it applies, and can be run alone (`link/ssh.sh yes`). Shared helpers are in `link/lib.sh`. New logic goes in a part, not in `link.sh`. + +### Theme System +A sophisticated theming system managed by `bin/theme` coordinates color schemes across multiple applications: +- Supports both light and dark modes +- Automatically detects system theme preferences +- Coordinates colors for: alacritty, tmux, neovim, bat +- Uses color configuration files in `config/*/colors/` directories +- Stores theme preferences in `$XDG_DATA_HOME/{light,dark}-colors` + +### Secret Management +`szecret`, an `age`-based secret manager: +- The tool is here; the store it reads -- `.age` files and the public key they + are encrypted to -- is not, and is found at `~/.local/share/szecret/` or + through `$SZECRET_STORE`. See `secrets/README.md` +- Accessed via the `szecret` command (symlinked from `secrets/main.sh`) +- Supports listing, getting, and putting secrets +- Used to manage API keys, tokens, and credentials + +### Shell Environment + +The shell config is split into **two layers**. Which layer something belongs in +is decided by one question: *must this run in a shell that has no terminal?* + +**1. Environment layer — `config/env`.** PATH, XDG, locale, tool homes. Loaded by +every shell in every mode: + +| shell | route | +|---|---| +| zsh, any mode | `~/.zshenv` (the only startup file non-interactive zsh reads) | +| zsh, login | `~/.zprofile` as well — a second pass, see below | +| bash, login | `~/.profile` → `config/profile` sources `~/.env` | +| bash, non-interactive | `$BASH_ENV`, exported by `config/env` itself | + +Rules for this file, enforced by its header comment: POSIX sh only (bash and zsh +both source it), **no forks** (no `$(...)`, no external commands — use `case` and +parameter expansion), no side effects, idempotent, `set -u` safe. It costs ~4ms. + +**Why `config/env` is loaded twice under zsh.** macOS runs `path_helper` from +`/etc/zprofile`, which rebuilds PATH with `/etc/paths` (`/usr/bin`, `/bin`, …) +at the front and whatever was already on PATH behind it. zsh reads `~/.zshenv` +*before* `/etc/zprofile`, so the precedence `config/env` establishes there is +inverted moments later — that is how `/usr/bin/vi` came to shadow the `~/.bin/vi` +wrapper, and `/usr/bin/git` the Homebrew one. `config/zprofile` loads `config/env` +again, after `path_helper`, to put it back. Two things make that work, and both +are load-bearing: the file is idempotent, and its `_env_prepend`/`_env_append` +**move** an entry that is already on PATH rather than leaving it in place. Order +is therefore decided entirely by the sequence of calls in `config/env`, from any +starting PATH. The flip side: never hand `_env_append` a system directory, or it +gets demoted to the end. Login bash needs none of this — `/etc/profile` runs +`path_helper` *before* `~/.profile`. + +**2. Interactive layer — `config/profile`.** Aliases, history, `stty`, `GPG_TTY`, +theme, jenv, and every `~/.config/shell/profile.d/*.sh`, which is where +another layer links in its own setup, secrets included. +Shared by both shells, +with a per-shell file on top: + +- `config/zshrc`: zsh keybindings, completions, prompt; sources `~/.profile` +- `config/bashrc`: bash history opts, shopts, completion, prompt; sources `~/.profile` + +`config/zprofile` is *not* part of this layer despite the name — zsh reads +`~/.zprofile` for login shells that are not interactive too, so it holds only the +environment-layer second pass described above. + +zsh and bash disagree about a *login interactive* shell: zsh reads `~/.zprofile` +**and** `~/.zshrc`, but bash reads only `~/.profile` and never `~/.bashrc`. So +`config/profile` and `config/bashrc` source each other, guarded both ways by +`__dotfiles_profile` / `__dotfiles_bashrc` so exactly one direction fires. Touch +that handshake carefully — getting it wrong is an infinite loop at login. + +Host-local overrides follow the same split: `~/.localenv` (every shell, cheap) +vs `~/.localprofile` (interactive). + +**Do not move interactive-layer content into `config/env`.** Two concrete +reasons, both measured: `export GPG_TTY=$(tty)` yields the literal string +`not a tty` outside a terminal, and zsh — unlike bash — *expands aliases in +non-interactive shells*, so `alias ls='lsd'` would silently change the output of +every script calling `ls`. + +Key environment features: +- XDG Base Directory compliance +- Comprehensive PATH management with `pathadd`/`pathapp` functions +- Development tool support (node, go, rust, java, python) +- Custom aliases and utility functions +- Performance profiling capabilities + +### Utility Scripts +The `bin/` directory contains various utility scripts for: +- Theme management (`theme`) +- Notification sending (`notify`) +- Development tools (`vi` wrapper, `smack`, `pyexec`) +- System utilities (`wifi`, `default-browser`, `truecolour-test`, `open` — macOS-style `open` for Linux desktops; hands over to `/usr/bin/open` on macOS) + +## Development Notes + +- The repository assumes a Unix-like environment (macOS/Linux) +- Uses bash for scripts with `set -euo pipefail` for safety +- Follows XDG Base Directory specification where possible +- Heavy integration with Homebrew on macOS +- Supports both local and external source directories (e.g., `/Volumes/Sense/src`)
\ No newline at end of file |
