Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1ebc146
Personalize config for this machine
Tokien133 Aug 28, 2026
78c730f
Add simpler Cmd+D pane-split shortcuts
Tokien133 Aug 28, 2026
e750e4a
Add tmux to Homebrew brews and fix darwin-rebuild PATH resolution
Tokien133 Aug 28, 2026
5f8762c
Add WezTerm tab/pane keybindings and enable Claude Code agent push no…
Tokien133 Aug 28, 2026
fd22e5c
Add tools.sh to make the kunchenguid agentic toolkit reproducible
Tokien133 Aug 28, 2026
f383073
Add reusable HANDOFF.md template for multi-session project continuity
Tokien133 Aug 29, 2026
33bfb92
Enable lavish-axi SessionStart hook, ignore skills-lock.json
Tokien133 Aug 29, 2026
d889eee
Fix dangling Homebrew zsh completion symlinks after nix-homebrew acti…
Tokien133 Aug 29, 2026
2e5306a
Symlink ~/AGENTS.md to the shared home/AGENTS.md file
Tokien133 Aug 29, 2026
b656e5a
Add treehouse worktree config and wezterm keybindings notes
Tokien133 Aug 29, 2026
609b8d4
no-mistakes(document): Update README symlink-targets note for new ~/A…
Tokien133 Aug 29, 2026
ab15813
Merge pull request #1 from Tokien133/fix/homebrew-completions-symlink
Tokien133 Aug 30, 2026
fa4aa1c
Add tmux config with status bar at top
Tokien133 Aug 30, 2026
d343cb2
Add separator between tmux windows for tab-like look
Tokien133 Aug 30, 2026
eb65827
Match tmux status bar colors to WezTerm's rose-pine-moon theme
Tokien133 Aug 30, 2026
8dc4540
Switch to rounded pill-style tabs for tmux windows
Tokien133 Aug 30, 2026
ab05af2
Symlink tmux config directory via home-manager
Tokien133 Aug 30, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ result-*

# no-mistakes local validation state - never commit in this public repo
.no-mistakes/
skills-lock.json

# treehouse worktree pool - local runtime state, never commit
.treehouse/
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ If you don't use it, just remove it from `brews` in your copy.

**Heads-up:**

- `home/AGENTS.md` is my personal agent policy, and `home.nix` installs it for Claude, Codex, and opencode.
- `home/AGENTS.md` is my personal agent policy, and `home.nix` installs it as `~/AGENTS.md` and for Claude, Codex, and opencode.
If you clone this repo, you'd silently inherit my agent instructions - edit or delete `home/AGENTS.md` if you don't want that.
- The `cc` and `co` shell aliases in `home.nix` are high-agency shortcuts: `claude --dangerously-skip-permissions` and `codex --full-auto`.
They're convenient for me, but know what they do before you use them.
Expand Down Expand Up @@ -180,6 +180,16 @@ Both packages execute with your full user permissions and must be trusted like a

Home Manager deliberately does not manage `~/.pi/agent` itself, or Pi authentication, sessions, trust decisions, caches, npm/git package trees, or any other runtime state. The model overrides contain no credentials or endpoint settings, do not choose a default model, and only take effect after you authenticate Pi yourself. This remains an additive post-video layer: it does not install Pi, a launcher, or package source code into this repository.

## Optional agentic toolkit

A handful of [kunchenguid](https://github.com/kunchenguid) tools built for AI-assisted development - `treehouse`, `no-mistakes`, `gnhf`, `lavish-axi`, `gh-axi`, and `firstmate` - aren't Homebrew formulae/casks, so `rebuild.sh` can't install or restore them. They're personal, opt-in additions, not vendored by this config. Install (or reinstall on a new Mac) with:

```sh
./tools.sh
```

Safe to re-run - it skips anything already installed. `firstmate` is cloned to `~/dev/firstmate` but never auto-started; `cd` into it and run your agent CLI there when you want to use it.

## Notes

The first time you launch `nvim`, it bootstraps [lazy.nvim](https://github.com/folke/lazy.nvim) by cloning plugins from GitHub.
Expand Down
25 changes: 25 additions & 0 deletions configuration.nix
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,43 @@
nix-homebrew = {
enable = true;
inherit user;
autoMigrate = true;
};
# nix-homebrew only symlinks Library/Homebrew into the Nix store, not the
# top-level `completions` dir. That leaves Homebrew's own zsh completion
# symlinks dangling (e.g. share/zsh/site-functions/_brew ->
# ../../../completions/zsh/_brew), which makes every new zsh session print
# compinit errors. Recreate the missing symlink on every activation, since
# nix-homebrew rebuilds /opt/homebrew/Library from scratch each time.
system.activationScripts.postActivation.text = ''
brew_store_dir="$(dirname "$(dirname "$(readlink -f /opt/homebrew/Library/Homebrew)")")"
ln -sfn "$brew_store_dir/completions" /opt/homebrew/completions
'';
homebrew = {
enable = true;
onActivation.cleanup = "zap"; # remove anything not listed here
onActivation.autoUpdate = true;
onActivation.extraFlags = [ "--force" ];
taps = [
"supabase/tap"
];
brews = [
"herdr"
"gh"
"neovim"
"pnpm"
"sox"
"supabase/tap/supabase"
"tmux"
"vercel"
"whisper-cpp"
];
casks = [
"wezterm"
"claude-code"
"opensuperwhisper"
"visual-studio-code"
"battery"
];
};
}
3 changes: 2 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
let
# The one username line to change if this isn't your machine.
# bootstrap.sh offers to rewrite this for you if your macOS username differs.
user = "kunchen";
user = "jakobsteenberg";
in
{
darwinConfigurations."mac" = nix-darwin.lib.darwinSystem {
Expand All @@ -30,6 +30,7 @@
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.backupFileExtension = "backup";
home-manager.extraSpecialArgs = { inherit user; };
home-manager.users.${user} = import ./home.nix;
}
Expand Down
20 changes: 20 additions & 0 deletions home.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ in
};
};

programs.git = {
enable = true;
settings = {
user.name = "Jakob";
user.email = "[email protected]";
core.editor = "code --wait";
diff.tool = "vscode";
"difftool \"vscode\"".cmd = "code --wait --diff $LOCAL $REMOTE";
merge.tool = "vscode";
"mergetool \"vscode\"".keepBackup = false;
difftool.prompt = false;
"credential \"https://github.com\"".helper = [ "" "!/opt/homebrew/bin/gh auth git-credential" ];
"credential \"https://gist.github.com\"".helper = [ "" "!/opt/homebrew/bin/gh auth git-credential" ];
};
};

programs.starship = {
enable = true;
settings = {
Expand All @@ -56,6 +72,8 @@ in
# Edit-in-place: the real file stays in my repo, ~/.config just points at it.
home.file.".config/wezterm".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/wezterm";
home.file.".config/tmux".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/tmux";
home.file.".config/nvim".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.config/nvim";
home.file.".config/herdr".source =
Expand All @@ -73,6 +91,8 @@ in
home.file.".pi/agent/settings.json".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/.pi/agent/settings.json";

home.file."AGENTS.md".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
home.file.".claude/CLAUDE.md".source =
config.lib.file.mkOutOfStoreSymlink "${dotfiles}/home/AGENTS.md";
home.file.".codex/AGENTS.md".source =
Expand Down
17 changes: 16 additions & 1 deletion home/.claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
{
"theme": "dark-ansi",
"statusLine": {
"type": "command",
"command": "input=$(cat); model=$(echo \"$input\" | jq -r '.model.display_name'); used=$(echo \"$input\" | jq -r '.context_window.used_percentage // empty'); if [ -n \"$used\" ]; then printf \"%s | ctx: %.0f%% used\" \"$model\" \"$used\"; else printf \"%s\" \"$model\"; fi"
},
"theme": "dark-ansi",
"agentPushNotifEnabled": true,
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "lavish-axi",
"timeout": 10
}
]
}
]
}
}
11 changes: 11 additions & 0 deletions home/.config/tmux/tmux.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
set -g status-position top

# Rose Pine Moon palette, matching WezTerm's color_scheme in home/.config/wezterm/wezterm.lua
set -g status-style "bg=#232136,fg=#e0def4"
set -g window-status-style "fg=#908caa"
set -g window-status-current-style "bg=#c4a7e7,fg=#232136,bold"

# Pill-shaped "tabs": rounded Nerd Font caps around each window segment.
set -g window-status-format "#[fg=#44415a,bg=#232136]#[fg=#908caa,bg=#44415a] #I:#W #[fg=#44415a,bg=#232136]"
set -g window-status-current-format "#[fg=#c4a7e7,bg=#232136]#[fg=#232136,bg=#c4a7e7,bold] #I:#W #[fg=#c4a7e7,bg=#232136]"
set -g window-status-separator " "
87 changes: 87 additions & 0 deletions home/.config/wezterm/wezterm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,93 @@ config.macos_window_background_blur = 50
config.hide_tab_bar_if_only_one_tab = true
config.window_decorations = "RESIZE"

-- Simpler, layout-independent pane splits (iTerm2-style) alongside the stock defaults.
config.keys = {
{ key = "d", mods = "CMD", action = wezterm.action.SplitHorizontal({ domain = "CurrentPaneDomain" }) },
{ key = "d", mods = "CMD|SHIFT", action = wezterm.action.SplitVertical({ domain = "CurrentPaneDomain" }) },
{
key = "O",
mods = "CMD|SHIFT",
action = wezterm.action_callback(function(window, pane)
-- Explicit --pane-id because the background process doesn't inherit
-- the WEZTERM_PANE env var that the CLI would otherwise rely on.
wezterm.background_child_process({
"wezterm",
"cli",
"move-pane-to-new-tab",
"--pane-id",
tostring(pane:pane_id()),
})
end),
},
-- Merge the tab to the left of the active one into the active pane as a split,
-- e.g. with 6 and 7 open and 7 active, pulls 6 in to make one tab with two panes.
{
key = "M",
mods = "CMD|SHIFT",
action = wezterm.action_callback(function(window, pane)
local mux_window = window:mux_window()
local tabs = mux_window:tabs_with_info()

local active_index
for i, t in ipairs(tabs) do
if t.is_active then
active_index = i
break
end
end

local prev_tab = tabs[active_index - 1]
if not prev_tab then
window:toast_notification("WezTerm", "No previous tab to merge with", nil, 2000)
return
end

local other_pane_id
for _, info in ipairs(prev_tab.tab:panes_with_info()) do
if info.is_active then
other_pane_id = info.pane:pane_id()
end
end

-- Same --pane-id workaround as the move-pane-to-new-tab binding above:
-- background_child_process doesn't inherit WEZTERM_PANE.
wezterm.background_child_process({
"wezterm",
"cli",
"split-pane",
"--pane-id",
tostring(pane:pane_id()),
"--move-pane-id",
tostring(other_pane_id),
"--horizontal",
})
end),
},
-- Reorder the active tab one position left/right, e.g. to pull tab 7 to position 1.
{ key = "LeftArrow", mods = "CMD|SHIFT", action = wezterm.action.MoveTabRelative(-1) },
{ key = "RightArrow", mods = "CMD|SHIFT", action = wezterm.action.MoveTabRelative(1) },
-- Resize the active pane. ALT is the Option key on macOS.
{ key = "LeftArrow", mods = "CTRL|SHIFT|ALT", action = wezterm.action.AdjustPaneSize({ "Left", 1 }) },
{ key = "RightArrow", mods = "CTRL|SHIFT|ALT", action = wezterm.action.AdjustPaneSize({ "Right", 1 }) },
{ key = "UpArrow", mods = "CTRL|SHIFT|ALT", action = wezterm.action.AdjustPaneSize({ "Up", 1 }) },
{ key = "DownArrow", mods = "CTRL|SHIFT|ALT", action = wezterm.action.AdjustPaneSize({ "Down", 1 }) },
-- One-shot copy of the entire scrollback (not just the current viewport) to the clipboard,
-- so there's no need to manually drive Copy Mode's g / Shift+V / Shift+G / y sequence.
{
key = "C",
mods = "CMD|SHIFT",
action = wezterm.action.Multiple({
wezterm.action.ActivateCopyMode,
wezterm.action.CopyMode("MoveToScrollbackTop"),
wezterm.action.CopyMode({ SetSelectionMode = "Line" }),
wezterm.action.CopyMode("MoveToScrollbackBottom"),
wezterm.action.CopyTo("Clipboard"),
wezterm.action.CopyMode("Close"),
}),
},
}

-- Dim unfocused windows so the focused one is obvious at a glance.
local UNFOCUSED_FOREGROUND_TEXT_HSB = { hue = 1.0, saturation = 0.25, brightness = 0.45 }
local UNFOCUSED_WINDOW_BACKGROUND_OPACITY = 0.62
Expand Down
1 change: 1 addition & 0 deletions home/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Apply that same high standard to engineering excellence: lint, test failures, and test flakiness.
If you see one, even if it is not caused by what you are working on right now, still get it fixed.
- Before using "dynamic workflows", "ultra code" or any harness feature that immediately spawns a large swarm of subagents, always explain the tradeoffs and ask the user for explicit approval.
- For any project that spans multiple sessions, set up a living `docs/HANDOFF.md` status file (template: `~/dotfiles/templates/HANDOFF.md.template`), referenced from the project's AGENTS.md/CLAUDE.md so a fresh session finds it automatically. Edit its SESSION STATUS section in place at the end of a work session - never leave it stale, and never just append a new dated entry instead of updating it.

## Maintaining this file

Expand Down
2 changes: 0 additions & 2 deletions home/CLAUDE.md

This file was deleted.

95 changes: 95 additions & 0 deletions notes/wezterm-keybindings-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# WezTerm-tangentbord: sessionsstatus (2026-08-28)

Anteckning för att en ny (moln-)session ska kunna fortsätta där denna slutade.
Miljö: macOS, WezTerm 20240203-110809-5046fc22, tangentbordslayout "Swedish - Pro".
Config: `home/.config/wezterm/wezterm.lua`.

## Vad som gjordes idag

1. **Ny bindning `CMD+SHIFT+M`**: slår ihop den föregående (vänstra) fliken med
den aktiva som en pane-split i samma flik, via
`wezterm cli split-pane --pane-id <aktiv> --move-pane-id <andra> --horizontal`
(`--move-pane-id` finns i denna wezterm-version, verifierat med
`wezterm cli split-pane --help` och manuellt testat direkt via CLI - fungerar).
- Lua-callbacken använder `window:mux_window():tabs_with_info()` och
`tab:panes_with_info()` för att hitta föregående flikens aktiva pane-id.
- Ursprunglig version letade fel håll (nästa flik istället för föregående) och
misslyckades tyst på sista fliken - rättat.
- Visar nu en `toast_notification` om det inte finns någon flik att slå ihop med.
- **Status: bekräftat fungerande** ("Nu verkar det funka").

2. **Buggfix i en redan existerande bindning `CMD+SHIFT+C`** (helkopiering av
scrollback): `wezterm.action.CopyMode({ CopyTo = ... })` är fel syntax -
`CopyTo` är en egen toppnivå-action, inte en `CopyModeAssignment`-variant.
Orsakade ett fatalt config-laddningsfel (traceback i loggen). Rättat till
`wezterm.action.CopyTo("Clipboard")` (användaren ändrade sedan värdet själv
från `"ClipboardAndPrimarySelection"` till `"Clipboard"`).

## Olöst: reload-tangenterna (CMD+R / CTRL+SHIFT+R / CMD+SHIFT+R) verkar inte fungera

Användaren rapporterar att ingen av dessa reagerar:
- `CMD+SHIFT+R` - **är aldrig bunden till reload som standard** i WezTerm
(min ursprungliga instruktion om detta var fel).
- `CTRL+SHIFT+R` - default-bindning till `ReloadConfiguration`, borde fungera.
- `CMD+R` (`SUPER+r`) - default-bindning till `ReloadConfiguration`, borde fungera.

### Redan uteslutet
- **Config-filen själv**: `wezterm show-keys --lua` visar att alla tre riktiga
bindningarna (`CTRL+R`, `SHIFT|CTRL+R`, `SUPER+r`) är korrekt registrerade mot
`ReloadConfiguration`, inga konflikterande overrides tidigare i listan.
- **Allmän interception av CTRL+SHIFT-kombon**: `CTRL+SHIFT+L` (debug overlay,
`ShowDebugOverlay`) fungerar och visas synligt - alltså tar WezTerm emot
CTRL+SHIFT-kombinationer generellt. Problemet är specifikt kopplat till
R-tangenten, inte till modifierartangenterna.
- **Kända remapping-verktyg**: inga processer för Karabiner-Elements,
Hammerspoon, BetterTouchTool, Rectangle, Alfred, Raycast, skhd hittades
körande (`ps aux`).
- **`hidutil` key remapping**: `hidutil property --get "UserKeyMapping"` -> null.
- **macOS systemgenvägar** (`defaults read com.apple.symbolichotkeys
AppleSymbolicHotKeys`): ingen post med `keyCode == 15` (R-tangentens
Carbon-keycode) hittades bland aktiva genvägar.
- **OpenSuperWhisper** (körande bakgrundsapp, diktering/Whisper-transkribering):
`~/Library/Preferences/ru.starmel.OpenSuperWhisper.plist` visar
`KeyboardShortcuts_toggleRecord` = Option + \` (grave-tangenten,
carbonKeyCode 50, carbonModifiers 2048=Option) och `modifierOnlyHotkey` =
`"rightOption"` (tryck/släpp höger Option ensam). Användaren bekräftade att
det är höger Option de faktiskt använder för att starta inspelning - inte R.
**Definitivt uteslutet.**
- **ChatGPT-skrivbordsappen** (körande, `com.openai.chat.plist`):
`toggleLauncher` = Option+Space, `toggleAttachedLauncher` = Option+Shift+1,
`openSettings` = Cmd+`,`. Ingen R-bindning. Sannolikt inte boven, men inte
testat genom att faktiskt stänga appen och pröva om.

### Inte testat än / nästa steg
- **Stäng övriga körande appar en i taget och testa `CMD+R` mellan varje**:
NordVPN, Spotify, WhatsApp, Mail, Messages, Notes, Passwords, Safari,
Activity Monitor, TextEdit, Tips, System Settings. (Fullständig lista över
körande GUI-appar vid felsökningstillfället, hämtad via
`osascript -e 'tell application "System Events" to get name of every process
whose background only is false'`: Safari, ChatGPT, Terminal, Claude, Mail,
TextEdit, Activity Monitor, Notes, Messages, Passwords, Finder, NordVPN,
Spotify, WhatsApp, wezterm-gui, System Settings, OpenSuperWhisper, Tips.)
- **Starta om wezterm-gui med utökad loggning** (t.ex. `WEZTERM_LOG=trace`)
för att se om R-tangenttryckningen ens når WezTerm-processen. Kräver att
den körande instansen dödas och startas om, vilket stänger alla användarens
öppna flikar/paneler - måste godkännas av användaren innan det görs.
- Loggfilen för den aktiva processen (`~/.local/share/wezterm/wezterm-gui-log-<pid>.txt`,
hitta rätt pid via `ps aux | grep wezterm-gui`) visar bara ERROR-nivå som
standard - lyckade reloads syns inte där, bara Lua-fel.

## Olöst: hur verifiera att en manuell reload faktiskt sker

Föreslagen men ännu inte genomförd test:
1. Sätt temporärt `config.automatically_reload_config = false` i wezterm.lua
(så att sparning inte redan triggar auto-reload och maskerar resultatet).
2. Ändra något visuellt påtagligt, t.ex. `config.window_background_opacity`
till `0.3`.
3. Spara filen (inget ska hända ännu).
4. Tryck den tangentkombo som testas (t.ex. `CMD+R`).
5. Om bakgrunden blir mer genomskinlig fungerade den tangenten. Om inte -
den fastnar fortfarande.
6. Återställ `automatically_reload_config` (ta bort raden, standard är `true`)
och `window_background_opacity` till `0.8` efteråt.

Användaren har inte bett mig genomföra detta än - fråga om de vill det, eller
om de redan testat manuellt.
15 changes: 14 additions & 1 deletion rebuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,17 @@
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
ln -sfn "$DIR" ~/.dotfiles
exec sudo darwin-rebuild switch --flake ~/.dotfiles#mac
# sudo resets PATH to a secure default that excludes /run/current-system/sw/bin,
# so a bare `darwin-rebuild` would not be found under sudo even when it resolves
# here. Resolve the absolute path first and invoke that instead. Fall back to
# the stable nix-darwin system-profile location when darwin-rebuild isn't on
# PATH at all (e.g. /run/current-system/sw/bin was never added to PATH).
if DARWIN_REBUILD="$(command -v darwin-rebuild 2>/dev/null)"; then
:
elif [ -x /run/current-system/sw/bin/darwin-rebuild ]; then
DARWIN_REBUILD=/run/current-system/sw/bin/darwin-rebuild
else
echo "error: darwin-rebuild not found on PATH or at /run/current-system/sw/bin/darwin-rebuild" >&2
exit 1
fi
exec sudo "$DARWIN_REBUILD" switch --flake ~/.dotfiles#mac
Loading