Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
**Claude seats (2026-09-22).** Two Claude Code logins live on the coordinator
and nowhere else: `cc` is `~/.claude` (the personal Claude Max login, config file
`~/.claude.json`), `cc2` is `~/.claude-work` (the leger.run Claude Max login,
config file `~/.claude-work/.claude.json`), selected only by `CLAUDE_CONFIG_DIR`
in the fish launchers (`home/dot_config/fish/config.fish`). Both share the same
skills and settings links; login, history, sessions and trust are per seat, so a
session id resumes only on the seat and from the cwd that created it
(`claude-sessions` fans out over the seats). Claude never saves workspace trust
for `$HOME`, so the launchers move into `$CLAUDE_ENVELOPE` (default `~/today`)
when typed from `~`; never start a seat in the home directory. Logins are hand
`/login`s, never a delivered secret (the old claude-credentials seed was removed
this day). The seat meters are `~/.local/state/tally-rewrite/meters/<seat>.json`.

**Physical seats (2026-09-16, supersedes older headless/client-only wording below).**
Tom is returning the coordinator to primary-desktop duty with two upright LG
5K displays side by side at scale 2. Both coordinator and client have
Expand Down
3 changes: 2 additions & 1 deletion docs/theme-switcher-2026-09-17.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ palette value does.
| consumer | joins via | live reload on `theme <name>` |
|---|---|---|
| kitty | `include ${HOME}/.config/theme/kitty.conf` (kitty.conf) | `kitten @ --to unix:@kitty-<pid> load-config` per instance (sockets from `/proc/net/unix`) |
| ghostty (cmux Browser panes) | `config-file = ?/home/tom/.config/theme/ghostty` LAST in ghostty/config.ghostty (a config-file loads after its parent, so the fragment wins; absolute because Ghostty resolves relative paths against the symlinked config's dir) | nothing — libghostty reads the config when cmux opens a surface; new panes follow |
| niri | `include optional=true "~/.config/theme/niri.kdl"` LAST in config.kdl; sections merge, later wins | `niri msg action load-config-file` |
| herdr | `[theme] name = "terminal"`: every token is an ANSI slot | nothing — kitty reports the bg change via DEC 2031 (`CSI ?997;n`), herdr re-queries OSC 10/11/4 and repaints chrome + every pane |
| nvim | `lua/theme.lua` dofile()s `theme.lua`; catppuccin/bufferline/lualine read it | `nvim --server <sock> --remote-expr` → `require('theme').reload()` per instance |
| nvim | `~/.local/bin/nvim-lua/theme.lua` dofile()s `theme.lua`; catppuccin/bufferline/lualine read it | `nvim --server <sock> --remote-expr` → `require('theme').reload()` per instance |
| fish | `conf.d/colors.fish` sources the fragment; re-sources at the next prompt when the pointer moved | automatic (no universal variables: `fish_variables` is tracked) |
| starship | already ANSI-named; the one `#F47B85` became `red` | follows kitty |
| Claude Code | `theme` key in `~/.claude.json` ← `meta` `claude_code=` | written tmp+rename; takes effect on next start |
Expand Down
44 changes: 37 additions & 7 deletions home/dot_config/fish/config.fish
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,43 @@ alias l.="eza -a | grep -E \"^\.\""
alias cd='z'
# zi command

# Claude Code
alias cc='claude --dangerously-skip-permissions'
alias cac='claude --continue --dangerously-skip-permissions'
# Second Claude account (work). CLAUDE_CONFIG_DIR isolates login, history and
# state in ~/.claude-work; skills/settings are the same dotfiles links (home.nix).
alias cc2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --dangerously-skip-permissions'
alias cac2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --continue --dangerously-skip-permissions'
# Claude Code: two seats, one launcher shape (2026-09-22).
# cc / cac -> ~/.claude the personal Claude Max login (gmail org)
# cc2 / cac2 -> ~/.claude-work the leger.run Claude Max login
# CLAUDE_CONFIG_DIR isolates login, history and state per seat; skills and
# settings are the same dotfiles links for both (home.nix). TALLY_SEAT names the
# seat to hooks and receipts. Claude Code never saves workspace trust for $HOME
# (docs, security: "trust acceptance is held for the current session only and
# is not written to disk"), so a seat started in ~ shows "Accessing workspace"
# on every launch. Both launchers therefore move into the desk envelope when
# typed from ~; the shell stays there afterwards, which is where the desk
# session's files belong (~/today/CLAUDE.md section 4).
set -q CLAUDE_ENVELOPE; or set -gx CLAUDE_ENVELOPE $HOME/today
function __claude_seat --description 'start one Claude seat outside $HOME'
set -l seat $argv[1]
set -l config_dir $argv[2]
set -l args $argv[3..]
if test (pwd) = $HOME
if not test -d $CLAUDE_ENVELOPE
echo "$seat: \$CLAUDE_ENVELOPE ($CLAUDE_ENVELOPE) does not exist; cd into a project first" >&2
return 1
end
builtin cd $CLAUDE_ENVELOPE
echo "($seat: started in $CLAUDE_ENVELOPE, not in ~)" >&2
end
# A terminal opened from inside a cc2 session already carries
# CLAUDE_CONFIG_DIR, so `cc` must clear it explicitly or it silently starts
# cc2 (measured 2026-09-22). Each seat sets its own value or none.
if test -n "$config_dir"
env CLAUDE_CONFIG_DIR=$config_dir TALLY_SEAT=$seat claude --dangerously-skip-permissions $args
else
env -u CLAUDE_CONFIG_DIR TALLY_SEAT=$seat claude --dangerously-skip-permissions $args
end
end
function cc; __claude_seat cc "" $argv; end
function cac; __claude_seat cc "" --continue $argv; end
function cc2; __claude_seat cc2 $HOME/.claude-work $argv; end
function cac2; __claude_seat cc2 $HOME/.claude-work --continue $argv; end
# Third Claude account (2026-09-05), same rotation: state in ~/.claude-3.
alias cc3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --dangerously-skip-permissions'
alias cac3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --continue --dangerously-skip-permissions'
Expand Down
83 changes: 83 additions & 0 deletions home/dot_config/ghostty/config.ghostty
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# ghostty — kitty.conf's twin (2026-09-17).
#
# The only Ghostty on the fleet today is libghostty inside cmux Browser's
# terminal panes (cmux-tui; ~/.local/opt/cmux-browser-nightly, not packaged yet).
# Its settings page's "Use Ghostty config" reads THIS file,
# $XDG_CONFIG_HOME/ghostty/config.ghostty. Every value below mirrors kitty.conf
# key for key, so a cmux pane looks like a kitty window — a kitty change
# belongs here too. Where Ghostty has no equivalent, the kitty key is named
# and the gap stated. The colours are NOT here: they join through the theme
# fragment at the bottom, like kitty's `include ${HOME}/.config/theme/kitty.conf`.

# shell fish — kitty.conf says why: the login shell is bash fleet-wide (#236),
# fish stays the terminal UI.
command = fish
shell-integration = fish

# Font — kitty's EXPLICIT families (2026-08-21): Liga SFMono Nerd Font, bold is
# SemiBold on purpose (the weight the fontconfig fallback actually served).
# Ghostty takes family + style, where kitty takes the full face name.
font-family = Liga SFMono Nerd Font
font-family-bold = Liga SFMono Nerd Font
font-style-bold = SemiBold
font-family-italic = Liga SFMono Nerd Font
font-style-italic = Italic
font-family-bold-italic = Liga SFMono Nerd Font
font-style-bold-italic = SemiBold Italic
# font_size 16.0 (2026-09-16): 14 + one legacy ctrl+= press.
font-size = 16
# disable_ligatures never: ligatures are Ghostty's default, nothing to set.

# Cursor: cursor_blink_interval 0. kitty's cursor_trail has no Ghostty
# equivalent (cursor_trail_color in the fragment is kitty-only).
cursor-style = block
cursor-style-blink = false

# window_padding_width 10
window-padding-x = 10
window-padding-y = 10

# detect_urls yes / open_url_with default (xdg-open here too).
# underline_hyperlinks always: Ghostty underlines a link on hover only.
link-url = true
# confirm_os_window_close 0
confirm-close-surface = false
# scrollback_lines 100000 — Ghostty caps scrollback in BYTES per surface
# (lazily allocated, compressed when idle): 100 MB ≈ 100k lines of 1 KB.
scrollback-limit = 100000000
# enable_audio_bell no — Ghostty's default rings the system bell; cmux may
# still flag the surface (attention/title), never sound.
bell-features = no-system,no-audio
# copy_on_select clipboard
copy-on-select = clipboard
# THE THIN-CLIENT CLIPBOARD CONTRACT (kitty.conf, 2026-09-11), the OSC 52
# half: a remote program may WRITE this seat's clipboard (write-clipboard) and
# a remote READ prompts (read-clipboard-ask). `clipboard-read = allow` would
# let anything the coordinator runs exfiltrate the clipboard silently, so it
# must never be set.
clipboard-write = allow
clipboard-read = ask

# map ctrl+equal / ctrl+minus — kitty's legacy increase_font_size step is a
# fixed +2.0pt (0.48.0 boss.py); Ghostty's default step is 1, so pass the amount.
keybind = ctrl+equal=increase_font_size:2
keybind = ctrl+minus=decrease_font_size:2
# kitty_mod+h / kitty_mod+g (kitty-scrollback.nvim) and enabled_layouts splits
# have no Ghostty side: cmux owns splits, tabs and the scrollback viewer.

# Frosted terminal (2026-09-16): background_opacity 0.96. The compositor blur
# rides on the kitty app-ids in niri's window-rules.kdl; cmux is a different
# app-id, so its panes are translucent without the frost until a rule lands.
background-opacity = 0.96
# kitty does not dim inactive splits; Ghostty's default is 0.7.
unfocused-split-opacity = 1

# Colours: the theme fragment home/theme.nix renders from home/themes/*.nix
# under ~/.config/themes/<name>/ghostty, joined through the ~/.config/theme
# pointer that ~/.local/bin/theme retargets. Ghostty resolves a relative
# config-file against the directory of THIS file — which is a symlink into the
# checkout — so the path is absolute; $HOME is /home/tom on every host
# (home.homeDirectory). `?` = no error while the fragment is not rendered yet.
# A config-file loads AFTER the file that names it, so the fragment wins.
# No live reload: cmux reads the config when it opens a surface.
config-file = ?/home/tom/.config/theme/ghostty
5 changes: 4 additions & 1 deletion home/dot_config/nvim/lua/plugins/lualine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ components.location = {
function M.setup()
-- Palette colours are resolved here, not at module load, so theme.reload()
-- can call setup() again after a `theme` switch.
local T = require('theme').palette()
-- pcall: a missing theme module must never cost the status line (noir
-- fallback values, the same as theme.lua's own).
local ok, theme = pcall(require, 'theme')
local T = ok and theme.palette() or { green = '#9BE963', red = '#F47B85' }
components.external_changes.color.fg = T.green
components.recording.color.fg = T.red
require('lualine').setup({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
-- lua/theme.lua — the palette every colour in this config comes from.
-- theme.lua — the palette every colour in the nvim config comes from.
--
-- Home Manager renders ~/.config/themes/<name>/theme.lua for each theme
-- (home/themes/*.nix); ~/.config/theme is the symlink ~/.local/bin/theme
-- retargets. This module dofile()s the current fragment and applies it to
-- catppuccin, bufferline and lualine — at startup from plugins.lua.in, and
-- again on `theme <name>`, which calls reload() over --remote-expr on every
-- running nvim. RAW (live-edit), see home/nvim.nix nvimRaw.
-- running nvim.
--
-- Lives in ~/.local/bin/nvim-lua/, not ~/.config/nvim/lua/ (2026-09-17): that
-- directory is ONE out-of-store link into the checkout (home.nix
-- `home.file.".local/bin"`) and init.lua adds it to package.path before
-- plugins load, so require('theme') resolves the moment the checkout has this
-- file. Under nvim/lua/ it needed its own per-file link from a rebuild, and
-- lualine.lua (already linked) failed with "module 'theme' not found" on any
-- machine whose checkout was ahead of its switch.
local M = {}

local FRAGMENT = vim.fn.expand('~/.config/theme/theme.lua')
Expand Down
1 change: 1 addition & 0 deletions home/home.nix
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ let
configDirs = [
"niri"
"kitty"
"ghostty" # kitty.conf's twin, read by libghostty in cmux Browser's panes
"fish"
"starship"
"yt-dlp"
Expand Down
5 changes: 3 additions & 2 deletions home/nvim.nix
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ let
"lua/plugins/nvim-tree.lua"
"lua/plugins/telescope.lua"
"lua/plugins/lualine.lua"
# the theme switcher's palette loader (docs/theme-switcher-2026-09-17.md)
"lua/theme.lua"
# NOT lua/theme.lua: the theme switcher's palette loader lives in
# ~/.local/bin/nvim-lua/ (a whole-dir RAW link on init.lua's package.path),
# so a checkout pulled ahead of its switch still finds it. See its header.
];

# every binary the nvim config shells out to (replaces mason; covers all plugin CLIs)
Expand Down
43 changes: 43 additions & 0 deletions home/themes/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,48 @@ let
inactive_border_color ${t.ground.base}
'';

# Ghostty (libghostty inside cmux Browser's terminal panes): the kitty
# fragment, key for key, in Ghostty's `key = value` grammar.
ghostty = t: ''
${header t "#"}
# Joined by ghostty/config.ghostty's `config-file = ?/home/tom/.config/theme/ghostty`.
background = ${t.ground.base}
foreground = ${t.fg}
selection-background = ${t.selection.bg}
selection-foreground = ${t.selection.fg}
cursor-color = ${t.cursor}
cursor-text = ${t.cursorText}

# black
palette = 0=${t.ansi.black}
palette = 8=${t.ansi.brightBlack}
# red
palette = 1=${t.red}
palette = 9=${t.red}
# green
palette = 2=${t.green}
palette = 10=${t.green}
# yellow
palette = 3=${t.yellow}
palette = 11=${t.yellow}
# blue
palette = 4=${t.blue}
palette = 12=${t.blue}
# magenta
palette = 5=${t.magenta}
palette = 13=${t.magenta}
# cyan
palette = 6=${t.cyan}
palette = 14=${t.cyan}
# white
palette = 7=${t.ansi.white}
palette = 15=${t.ansi.brightWhite}

# splits: as kitty, the divider is niri's INACTIVE window border.
split-divider-color = ${t.border.inactive}
unfocused-split-fill = ${t.ground.base}
'';

niri = t: ''
${header t "//"}
// Joined by niri/config.kdl's `include optional=true "~/.config/theme/niri.kdl"`,
Expand Down Expand Up @@ -168,6 +210,7 @@ rec {

render = t: {
"kitty.conf" = kitty t;
"ghostty" = ghostty t;
"niri.kdl" = niri t;
"colors.fish" = fish t;
"theme.lua" = lua t;
Expand Down
11 changes: 11 additions & 0 deletions hosts/coordinator/disko.nix
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
# Wiped blank before the physical move (no partition table, no bootloader),
# then laid out here.
#
# ── OWNERSHIP (Tom, 2026-09-19) ─────────────────────────────────────────────
# THIS DISK BELONGS TO SODIMO. It is one of exactly two sodimo-owned items in
# the fleet, the other being the `worker` chassis. It is here only because the
# 2026-08-30 transition fitted Tom's own 1TB (26051Y809195) into worker and
# moved this one across.
#
# It LEAVES with worker (2+ months out as of 2026-09-19; per FRONT-10 no agent
# invents or enforces that date). /home lives on it, so that departure is a
# migration off this disk, not an unplug — plan it as one. Afterwards the
# coordinator runs on its own 1TB anchor 25140U804698 with no secondary.
#
# ── The rule this disk exists to enforce ────────────────────────────────────
# The 1TB anchor holds the OS and only the OS: the nix store and everything
# NixOS derives from it, /etc, /var, and the local-models weight collection.
Expand Down
10 changes: 10 additions & 0 deletions hosts/nas/disko.nix
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
# generated media (media.nix). Random-I/O state belongs here: it would be
# wear on the eMMC and seek chatter on the HDD. The mountpoint move was
# non-destructive — same filesystem, never reformatted.
#
# ── In the 2026-09-19 disk shuffle ──────────────────────────────────────────
# This Fanxiang is Tom's and is a CANDIDATE TO MOVE to the fanless desk NUC:
# 256GB is ample for a seat that only runs Chromium. In that option the 1TB
# freed by the worker return lands here instead, taking /mnt/fast to 1TB.
# The alternative sends the freed 1TB straight to the NUC and leaves this
# disk alone. Undecided as of 2026-09-19; either way nothing is purchased.
# If this disk does move, /mnt/fast is offline in the interim — it was 49%
# of 234G at the 2026-09-18 census and carries PostgreSQL, Navidrome state,
# Immich generated media and the remote journal. Check those before moving.
disko.devices.disk.journal = {
type = "disk";
device = "/dev/disk/by-id/nvme-Fanxiang_S500Pro_256GB_26040259615000015";
Expand Down
16 changes: 16 additions & 0 deletions hosts/worker/disko.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
# 500GB paired against the coordinator's 1TB was the fleet's dual-node
# capacity ceiling.
#
# ── OWNERSHIP (Tom, 2026-09-19) ─────────────────────────────────────────────
# THIS DISK IS TOM'S, NOT SODIMO'S. sodimo owns exactly two items in this
# fleet: the `worker` chassis and the 500GB SN7100 260538801482 — and that
# 500GB is fitted in the COORDINATOR today, not here. When worker goes back
# (2+ months out as of 2026-09-19; per FRONT-10 no agent invents or enforces
# that date), the 500GB returns to this chassis and THIS 1TB COMES OUT.
#
# Do not read "worker's boot disk" as "unavailable". That is a filesystem
# fact, not an ownership fact, and conflating the two sent the silent-desk
# planning down a wrong path until it was corrected on 2026-09-19.
#
# The freed 1TB is the fleet's ENTIRE NVMe surplus. It is spoken for: either
# it becomes the fanless desk NUC's disk, or it upgrades the NAS /mnt/fast
# (256GB -> 1TB) and the NAS's Fanxiang 256GB goes to the NUC instead.
# Undecided. Standing constraint (Tom, 2026-09-19): NO NEW M.2 IS PURCHASED.
#
# ── Why the attr is `w1t` and NOT `main` ────────────────────────────────────
# disko derives GPT partition NAMES from the attr: `main` yields
# `disk-main-ESP` / `disk-main-root`. The coordinator's anchor uses those same
Expand Down
Loading