7.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this repo is
Master dotfiles repo for a Hyprland desktop on Arch Linux. It has two halves:
src/— a small interactive TypeScript CLI (install/update/remove) that manages packages and dotfiles.packages/— the actual dotfiles, laid out as GNU Stow packages (one top-level dir per app, mirroring$HOME, e.g.packages/waybar/.config/waybar/config).
The CLI is the only orchestration layer; it shells out to the real stow, pacman, and yay binaries rather than reimplementing symlinking or package management.
Commands
npm install
npm start install # interactive: choose packages, installs them, backs up conflicting files, symlinks into $HOME
npm start update # pacman -Syu + yay -Syu, then re-links (stow --restow) any new files
npm start remove # removes symlinks (stow -D), restores the most recent backup (does NOT uninstall packages)
npx tsc --noEmit # typecheck
npm test # runs test/ via Node's built-in test runner (node:test) through tsx
./docker/test.sh "npm test" # run the full suite, including stow-dependent tests skipped on a bare host
There is no build step — tsx runs the TypeScript directly.
Testing
test/*.test.ts uses Node's built-in node:test + node:assert (via tsx --test) — no test framework dependency was added. test/gpu.test.ts and test/packages.test.ts are pure unit tests. test/stow.test.ts exercises the real stow binary and real filesystem calls against a throwaway $HOME (a mkdtempSync dir, swapped in via process.env.HOME before stow.ts is dynamically imported, since os.homedir() reads $HOME and the module's path constants are computed at import time); each case is skipped when stow isn't installed, which is the case on a bare host — run ./docker/test.sh "npm test" to execute those for real inside a container that has stow.
docker/test.sh (see docker/Dockerfile) spins up a disposable Arch container with stow/nodejs/npm/git/sudo preinstalled and the repo copied to a writable path inside it, so npm install, Stow symlinks, and backups never touch the host. Run it with no args for an interactive shell, or pass a command string to run non-interactively.
Architecture
src/packages.ts— the package model. Each entry has atier(requiredinstalls with no prompt;recommendedis offered via checkbox), an optionalgroupfor mutually-exclusive alternatives (e.g.waybarvs an alternative both havinggroup: "bar", only one gets picked via a select prompt —barandlaunchercurrently each have just one member but are kept as groups so alternatives can be added the same way), and an optionaldirfor when a package's Stow directory name needs to differ from the package name.toDirs()converts chosen package names to their Stow directory names.src/gpu.ts—detectGpuVendors()shells out tolspciand matches VGA/3D controller lines against NVIDIA/AMD/Intel keywords (word-boundary regexes — a naive/amd|ati/iused to false-positive on the "ati" inside "Corporation");driverPackagesFor()maps detected vendors to their Arch driver packages.install.tsshows every detected vendor's packages in a pre-checked confirm prompt (installing all of them on hybrid/Optimus laptops), and if NVIDIA is kept,stow.ts'swriteNvidiaEnvConfig()writes~/.config/hypr/confd/nvidia.lua(machine-generated, not stowed) with the Wayland env vars Hyprland's NVIDIA docs recommend.src/stow.ts— all filesystem/process side effects live here: bootstrappingyayand oh-my-zsh/zinit if missing, installing packages viayay, enabling thesddmsystemd service (ensureSddmEnabled), backing up real (non-symlink) files that would collide with a Stow target into~/.dotfiles-backup/<timestamp>/, and wrappingstow/--restow/-D(always invoked with an explicit-d <PACKAGES_DIR>so behavior doesn't depend on cwd). Also reads/writes~/.config/dotfiles/state.json, which records which package dirs were chosen at install time —updateandremoveact on this list rather than re-prompting.src/install.ts,src/update.ts,src/remove.ts— the three subcommands, each a thin sequence of calls intostow.ts, using@clack/promptsfor interactive selection ininstall.ts.src/cli.ts— dispatchesinstall|update|removevia Node's built-innode:util.parseArgs.packages/hyprland/.config/hypr/hyprland.lua— Hyprland's config, in its current Lua format (hl.config/hl.bind/hl.monitor/hl.on/hl.env), not the oldhyprland.confini/hyprlang syntax deprecated since Hyprland 0.55. Easy detail to get wrong when editing — checkwiki.hypr.land(or the rawhyprland-wikiGitHub markdown) for current syntax rather than assuming the old ini style.require()inside it resolves dots in the module string as directory separators (standard Luapackage.pathbehavior) — that's why the machine-generated config dir is namedconfd, notconf.d: a literal dot in the directory name would makerequire("conf.d.foo")look forconf/d/foo.luainstead and silently fail (wrapped inpcall, so it fails with no visible error).packages/hyprpaper/.config/hypr/hyprpaper.conf— hyprpaper's config, in its current hyprlang block format (wallpaper { monitor = ... path = ... fit_mode = ... }), not the oldpreload =/wallpaper = monitor,pathkey=value syntax removed in hyprpaper 0.8.0.preloaddoesn't exist anymore at all. Verify againstsrc/config/ConfigManager.cppin thehyprwm/hyprpaperrepo (m_config.addSpecialConfigValue(...)calls list every valid key) rather than assuming the old ini style — same trap ashyprland.luaabove.packages/foot/.config/foot/foot.ini— the[colors]section was renamed to[colors-dark]in foot 1.26.0 (with a new[colors-light]counterpart, selected viainitial-color-theme=light);[colors]/[colors2]still work but print a deprecation warning on every launch.
Pinned reference versions (Arch, checked 2026-07-07)
These packages have had breaking, from-scratch config rewrites in living memory — don't assume a config snippet found online (or from an older revision of this repo) is still valid without checking the installed version against the package's current source/changelog:
| package | version | note |
|---|---|---|
hyprland |
0.55.4 | Lua config (hyprland.lua) since 0.55; older configs use hyprland.conf hyprlang/ini syntax. |
hyprpaper |
0.8.4 | Rewritten onto hyprtoolkit in 0.8.0 ("configs are broken and much simplified" — upstream's own words); new block-based wallpaper { } syntax, preload removed. |
foot |
1.27.0 | [colors]/[colors2] renamed to [colors-dark]/[colors-light] in 1.26.0; old names still work but warn. |
Other managed packages (waybar 0.15.0, mako 1.11.0, hyprlauncher 0.1.6, kitty 0.47.4, foot 1.27.0, zsh 5.9.1, xdg-desktop-portal-hyprland 1.3.12, sddm 0.21.0, stow 2.4.1) are recorded here for reference, but haven't been confirmed to carry a similar breaking-rewrite risk — re-check versions with pacman -Qi <pkg> before trusting this table, since this is a rolling-release Arch machine and it will drift.
Adding a new app
Add a directory under packages/ mirroring its $HOME layout (e.g. packages/foo/.config/foo/config), then add a matching entry to src/packages.ts (dir if the Stow directory name differs from the package name, group if it's a mutually-exclusive alternative to another package).
Backup/restore
Backups are stored under ~/.dotfiles-backup/<timestamp>/, mirroring the relative path of whatever real file was moved aside before stowing. remove restores only the most recent backup automatically; restoring an older one is a manual cp -a ~/.dotfiles-backup/<timestamp>/. ~/.