490 Commits

Author SHA1 Message Date
75c29eb12b Remove codex desktop from Lua branch 2026-04-28 20:52:51 -07:00
c194e91dc1 Keep pending scratchpads on invoking workspace 2026-04-28 20:52:38 -07:00
b52b2bcb57 Deploy Hyprland config from Lua branch source 2026-04-28 20:52:38 -07:00
ddb77a5a52 Hydrate minimized windows from hidden workspace 2026-04-28 20:52:38 -07:00
edbe9603f0 Make monocle directional focus deterministic 2026-04-28 20:52:38 -07:00
8d1d78dc25 Update hyprexpo Lua branch lock 2026-04-28 20:52:38 -07:00
3eb05515a1 Restore hyprexpo on Hyprland Lua branch 2026-04-28 20:52:38 -07:00
016e0aadfe Implement Hyprland Lua migration 2026-04-28 20:52:38 -07:00
c5a0ddd7b1 Remove codex desktop 2026-04-28 20:51:24 -07:00
d5abefe15e Fix taffybar HLint hints 2026-04-28 11:39:09 -07:00
4dfaabc569 Add Claude usage widget to taffybar 2026-04-28 11:32:53 -07:00
86d32eb0c8 Fix taffybar display environment startup 2026-04-28 10:35:47 -07:00
119b250bcc fix(sni): avoid duplicate blueman applet unit 2026-04-28 10:23:51 -07:00
63e5d1636d chore(gitignore): ignore home-manager backups 2026-04-28 10:23:51 -07:00
ba9cc6b811 chore(codex): trust keepbook project 2026-04-28 10:23:51 -07:00
c448b1d106 feat(nix): add codex desktop launcher 2026-04-28 10:23:51 -07:00
8ca8492b3b Update flake lock 2026-04-28 10:16:49 -07:00
c62d5df036 Include ignored files in Projectile file search 2026-04-28 10:15:21 -07:00
2ae2f56889 Use stacked OpenAI usage widget 2026-04-27 14:37:02 -07:00
f4387182e6 Abstract WM status bar references 2026-04-27 13:54:25 -07:00
b8ece14ea0 Document tiling WM bindings 2026-04-27 13:49:44 -07:00
ba2b24d436 Add tiling WM experience spec v1 2026-04-27 13:14:43 -07:00
975c9701ce Fix Gmail tray OAuth at login 2026-04-27 12:53:33 -07:00
92637b30e4 Propagate taffybar OpenAI usage widget 2026-04-27 12:53:26 -07:00
4e9e2408b5 nix: refresh flake inputs after rebase 2026-04-27 11:24:37 -07:00
e799e7876a taffybar: update vendored checkout 2026-04-27 11:23:59 -07:00
dbaf9af3b6 nixos: patch nixified-ai comfyui overlay 2026-04-27 11:23:20 -07:00
89bfcba9fe nixos: adjust services for updated nixpkgs 2026-04-27 11:23:20 -07:00
3966f7acda nix: fix shared overlay package paths 2026-04-27 11:23:20 -07:00
31504d8e5f nixos: enable ai stack on ryzen-shine 2026-04-27 11:22:50 -07:00
c8941870e2 nix: add shared binary caches 2026-04-27 11:22:50 -07:00
3bcbb70494 taffybar: follow vendored flake inputs 2026-04-27 11:22:50 -07:00
1a09aa134f zsh: add safe ncdu helper 2026-04-27 11:22:50 -07:00
431b4da1d1 codex: trust ai celeb project 2026-04-27 11:22:50 -07:00
edc6e0309b Disable Hyprland idle lock 2026-04-27 11:22:50 -07:00
9c901f84a3 Switch keepbook flake input from local path to GitHub
The local path: input broke system.autoUpgrade since the path
doesn't exist when building from the GitHub flake URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 11:10:42 -07:00
156d04a4a8 Add MPG341CX OLED autorandr profile 2026-04-27 09:47:22 -07:00
fe13ddd907 Remove Karabiner configuration 2026-04-27 09:04:24 -07:00
36a08c5c26 Add mac-demarco-mini syncthing config 2026-04-26 22:31:34 -07:00
f15aea9a9c Improve MPG341CX input toggle detection 2026-04-26 21:25:17 -07:00
c3c0d54da4 Move git-sync home config to shared module 2026-04-26 20:55:15 -07:00
9c2f244309 Add monitor input toggle keybindings 2026-04-26 18:42:07 -07:00
b596c38cb9 Enable Tailscale on macOS 2026-04-26 18:07:03 -07:00
cde795dcae Add MPG341CX input switcher 2026-04-26 17:39:31 -07:00
31fbcf73ac Configure macOS window tooling 2026-04-26 15:51:27 -07:00
1b8c54d722 Manage GTK CSS with Home Manager 2026-04-26 15:12:53 -07:00
0ed6b7f3e1 Add Qwen Rapid ComfyUI workflow 2026-04-26 13:51:30 -07:00
1a7dd966f9 Add monitor input toggle 2026-04-26 13:43:54 -07:00
5ca4d2745e Add zellij agent helpers 2026-04-26 13:43:54 -07:00
3a5aaa9351 Update keepbook NixOS input 2026-04-26 13:43:27 -07:00
7bec7d53d3 codex: disable vercel plugin 2026-04-26 11:54:01 -07:00
4f4c1b0a5c flakes: update git-sync-rs locks 2026-04-25 18:05:36 -07:00
bcee95fc23 darwin: install Emacs from Nix 2026-04-25 18:05:12 -07:00
1b7c6acf3f darwin: align Homebrew with cask tap 2026-04-25 18:04:53 -07:00
1a06382365 Fix Darwin switch after remote reconciliation 2026-04-25 17:34:58 -07:00
222228742f Fix Darwin dotfile symlink targets 2026-04-25 17:26:51 -07:00
d931e668da darwin: disable spotlight hotkeys 2026-04-25 16:49:20 -07:00
f6423481b8 nix: introduce shared nix modules 2026-04-25 16:49:20 -07:00
aac1dcc78a chore: ignore nix build results 2026-04-25 16:49:20 -07:00
aa035663e9 darwin: update codex and home setup 2026-04-25 16:49:19 -07:00
4b97e6c5f1 codex: manage generated skills with home-manager 2026-04-25 16:49:12 -07:00
5907395512 codex: keep skills under dotfiles symlink 2026-04-25 16:49:12 -07:00
1bc595dc13 codex: externalize generated system skills 2026-04-25 16:49:12 -07:00
2ac7faf884 Remove legacy zsh startup files 2026-04-25 16:49:06 -07:00
ab2f057dd2 Set up GPG key import on nix-darwin 2026-04-25 16:49:06 -07:00
06cde76b84 Remove Gmail MCP server from Codex startup 2026-04-25 16:49:06 -07:00
7a07c2b308 changes from mac-demarco-mini on Sat Apr 18 19:05:32 PDT 2026 2026-04-25 16:49:06 -07:00
6d9d5265b9 chore(flake): update keepbook input 2026-04-24 23:17:11 -07:00
5f6b81c6c1 fix(sni): refresh kanshi patch offsets 2026-04-24 23:06:43 -07:00
ea6a127fa5 chore(flake): update inputs 2026-04-24 23:06:43 -07:00
f9b51ae635 fix(strixi-minaj): stabilize kanshi tray behavior 2026-04-24 23:06:43 -07:00
efcc20c730 fix(nixos): avoid tzupdate catch-up at boot 2026-04-24 23:06:43 -07:00
ddb0a85c68 Add multiplexer title helper 2026-04-24 17:16:56 -07:00
0b1d058417 Add zellij alongside tmux 2026-04-24 15:08:19 -07:00
ac1c958256 Update flake lock 2026-04-24 11:33:52 -07:00
a8d2e3bf36 feat(nixos): enable tts on ryzen-shine 2026-04-23 10:22:44 -07:00
6d6176d354 chore(nix): update flake inputs 2026-04-23 10:21:44 -07:00
002d36cff7 chore(flake): refresh locked inputs 2026-04-20 13:07:49 -07:00
64f84cc946 chore(flake): use local keepbook checkout 2026-04-20 13:07:49 -07:00
fb9b526be4 fix(keepbook): make history menu limit optional 2026-04-20 13:07:49 -07:00
7f1f34732a feat(hyprland): add ultrawide-only dock profile 2026-04-20 13:07:49 -07:00
499c7a1c34 fix(emacs): preserve org checkout directory name 2026-04-20 13:07:49 -07:00
89f3220933 chore(codex): trust NIXOS_SD workspace 2026-04-20 13:07:49 -07:00
b09f4d6131 nix: install coqui-tts-streamer 2026-04-20 13:00:15 -07:00
425d97a844 Fix Emacs Elpaca bootstrap and startup 2026-04-18 19:03:11 -07:00
f420c442d8 Update nix-darwin codex flake and switch config 2026-04-18 11:33:40 -07:00
c8a0b2920c chore(ryzen-shine): disable nixified.ai temporarily 2026-04-17 13:15:28 -07:00
13becd22f7 feat(nixified-ai): configure comfyui cuda model support 2026-04-17 13:14:48 -07:00
bae205a553 chore(ryzen-shine): disable railbird k3s temporarily 2026-04-17 13:14:15 -07:00
3cc412cb6b fix(k3s): stop rebuilds waiting on cluster readiness 2026-04-17 13:13:25 -07:00
b98c160268 feat(hyprland): add optional lua-config package stack 2026-04-17 13:12:50 -07:00
fce5ff3ea4 feat(sni): improve kdeconnect tray behavior 2026-04-17 13:12:14 -07:00
aa2ccc3c02 chore(codex): trust local work directories 2026-04-17 13:11:33 -07:00
4b2479afc5 chore: remove gtk settings backup 2026-04-15 12:39:16 -07:00
429d8cc850 feat: add Hyprland screensaver helper 2026-04-15 12:39:16 -07:00
dfbe961e3c chore: migrate Gmail tooling docs to gws 2026-04-15 12:39:16 -07:00
d78393ec35 chore: stop tracking generated checkout caches 2026-04-15 12:39:16 -07:00
8f974d11f6 Use nth-last-child for end widget pills 2026-04-15 12:39:16 -07:00
a61792a5d9 Record local taffybar color fix state 2026-04-15 12:38:25 -07:00
9442aef530 Commit current dotfiles changes 2026-04-15 12:35:27 -07:00
bcf70f0183 chore: adjust flake input pins 2026-04-15 11:04:06 -07:00
3298e50089 config: trust coqui streamer project 2026-04-15 11:03:12 -07:00
851849c4ac fix: simplify org-mode package recipe 2026-04-15 11:03:02 -07:00
a1c0fa68a7 config: deprioritize tailscale tray item 2026-04-15 11:02:51 -07:00
25f182ab99 chore: add bubblewrap to essential packages 2026-04-15 11:01:59 -07:00
cc7c159ee4 fix: configure Strixi dGPU desk profile 2026-04-15 11:01:16 -07:00
934f41d9aa fix: keep Hyprland workspace focus local 2026-04-15 10:59:01 -07:00
f49946a3f5 fix: launch separate ghostty instances 2026-04-15 10:57:03 -07:00
546ea66941 fix: support POST requests in coqui-read 2026-04-15 10:55:44 -07:00
51b9868ae4 feat: add KEF speaker control command 2026-04-15 10:55:28 -07:00
7c30537caf chore: update taffybar submodule 2026-04-15 10:53:16 -07:00
34f30390a9 fix: patch kanshi-sni reconnect handling 2026-04-15 10:45:22 -07:00
5d6a289e42 dotfiles: add coqui-read helper 2026-04-14 00:45:26 -07:00
58e45bda3c nixos: enable local coqui tts 2026-04-14 00:44:44 -07:00
e0d053b0d9 nixos: vendor local package definitions 2026-04-14 00:41:08 -07:00
bd958027a9 taffybar: restore end-widget color wraparound 2026-04-14 00:21:54 -07:00
7de1b406b2 Disable Mullvad VPN on ryzen-shine 2026-04-13 13:13:15 -07:00
34b02c927a taffybar: restore single css entrypoint loading 2026-04-13 13:13:15 -07:00
337ee6872a Fix taffybar startup and restore pill colors 2026-04-12 15:23:20 -07:00
4107edcc55 Update local desktop and secrets configuration 2026-04-12 15:00:09 -07:00
f3fd6a7d95 Update vendored taffybar 2026-04-12 15:00:09 -07:00
26627e012f Fix taffybar host CSS composition 2026-04-12 15:00:09 -07:00
e10f8cd76c Fix Chrome remote debugging launchers 2026-04-12 15:00:09 -07:00
7e7ca69c37 Improve MIME defaults and Home Manager backups 2026-04-12 14:14:38 -07:00
c64045f2ee nixos: reduce ryzen-shine text scaling 2026-04-10 14:02:31 -07:00
76fd5fc86f hyprland: persist ryzen-shine kanshi display profile 2026-04-10 13:58:45 -07:00
34c3ad0ff8 taffybar: propagate config and flake updates 2026-04-10 13:40:30 -07:00
fa34081247 nixos: add railbird system user for k3s secrets 2026-04-10 13:03:56 -07:00
f49feaf378 Avoid package.el in org-wild-notifier async worker 2026-04-10 12:41:21 -07:00
d29b03c475 Tune ryzen-shine taffybar density 2026-04-10 12:28:46 -07:00
bbf1a99589 Tune ryzen-shine text scaling 2026-04-10 12:28:10 -07:00
9da5c5f345 revert: undo unintended submodule updates 2026-04-09 13:58:30 -07:00
7a17338936 chore(nixos): refresh flake inputs 2026-04-09 13:41:30 -07:00
5ad7ccf6bb chore(xmonad): bump submodule revisions 2026-04-09 13:41:27 -07:00
6cd3343614 chore(taffybar): remove split widget submodules 2026-04-09 13:41:22 -07:00
5db63a2bb0 nixos: update inputs and restore switch 2026-04-06 17:04:14 -07:00
2cd0febd2f docs: note picom debug log cleanup 2026-04-06 17:03:27 -07:00
dad13f5a19 chore(codex): lower default reasoning effort 2026-03-31 16:28:35 -07:00
0e0dba42ad chore(nixos): update t3code patch hash 2026-03-31 16:28:29 -07:00
e3c7c9d809 fix(nixos): normalize GPG key imports 2026-03-31 16:27:16 -07:00
c8650dd0ed feat(nixos): add Ghostty desktop entry 2026-03-31 16:26:44 -07:00
0fb27354a6 Add password reset skill 2026-03-31 12:15:14 -07:00
880d7047df hyprland: use builtin previous-workspace toggle 2026-03-31 11:24:50 -07:00
a55bc242a8 chore(taffybar): refresh pinned flake inputs 2026-03-30 12:40:07 -07:00
a127ae9522 fix(taffybar): make live checkout opt-in 2026-03-30 12:36:22 -07:00
313eb0db81 nixos: refresh flake inputs 2026-03-30 02:08:48 -07:00
72a035dd0c codex: update local config defaults 2026-03-30 02:08:10 -07:00
efe8993d26 codex: add imagegen and plugin-creator skills 2026-03-30 02:07:49 -07:00
f6d27a01c1 hyprland: cycle workspaces per monitor 2026-03-30 02:06:12 -07:00
f80d61cb74 Drop redundant vendored taffybar flake inputs 2026-03-30 01:58:27 -07:00
13f477f300 add t3code 2026-03-30 01:56:27 -07:00
16ae80b8a0 hyprland: serialize systemd session env import 2026-03-30 00:01:55 -07:00
3140b4f7c5 Set zlib library path in taffybar dev shell 2026-03-29 14:17:27 -07:00
ba1cc41e87 Fix taffybar dev shell tool resolution 2026-03-29 12:57:53 -07:00
a031d4d987 chore(taffybar): update submodule 2026-03-26 22:31:40 -07:00
48986b4892 docs(skills): add nixpkgs review guidance 2026-03-26 21:29:24 -07:00
ec39b3953f chore(flake): update pinned inputs 2026-03-26 21:28:48 -07:00
32c8508963 feat(home-manager): set GTK theme and git signing format 2026-03-26 21:28:00 -07:00
46e36d62b0 fix(nixos): patch quill ledger.did during build 2026-03-26 21:27:20 -07:00
9239eefff3 feat(desktop): improve launcher and window picker presentation 2026-03-26 21:26:19 -07:00
c1764fd0ee Fix Go mode eldoc setup 2026-03-25 13:06:24 -07:00
d9cbea9bcf fix org-agenda-api container startup 2026-03-25 11:45:55 -07:00
311f142d35 Update taffybar for bus-name churn dedupe fix 2026-03-23 22:24:58 -07:00
e5ae4da40d Add taffybar startup tray diagnostics 2026-03-22 07:27:12 -07:00
98ca024082 Update taffybar for stale tray rebuild fix 2026-03-21 13:09:16 -07:00
f9835c7cff Propagate taffybar tray startup fix through local flakes 2026-03-21 12:01:11 -07:00
8b8cd36ef1 Record taffybar tray startup fix commit 2026-03-20 00:27:01 -07:00
9972c16cf9 Update taffybar for tray startup fix 2026-03-20 00:26:42 -07:00
6e1db80e8a chore(users): drop explicit imalison uid 2026-03-19 13:49:56 -07:00
18fdda20e8 feat(nix): add git-blame-rank to essential packages 2026-03-19 13:47:45 -07:00
4823dc5e16 chore(nix): refresh flake inputs 2026-03-19 13:46:31 -07:00
23ae84e176 chore(taffybar): sync local overlay with vendored submodule 2026-03-19 13:44:09 -07:00
860e2b029c fix(git): use stable gh credential helper path 2026-03-19 13:19:58 -07:00
005545613e chore(codex): tune local assistant config 2026-03-19 13:19:44 -07:00
e51d2ec68f feat(skills): add OpenAI docs system skill 2026-03-19 13:19:06 -07:00
336c81178c chore(taffybar): bump vendored submodule 2026-03-15 14:15:23 -07:00
1f4bbbf92b chore(codex): trust local project directories 2026-03-15 14:13:13 -07:00
5d81e7c80c chore: update flake inputs 2026-03-14 10:44:41 -07:00
dd2c4fa8a4 chore: trust additional Codex projects 2026-03-14 10:44:18 -07:00
281e8b9a56 Improve Rust target cleanup skill 2026-03-12 13:25:53 -07:00
6a2f952fc3 feat(taffybar): build against local vendored packages 2026-03-10 14:12:36 -07:00
2d29056ed5 feat(hypridle): stop locking before dpms off 2026-03-10 14:11:37 -07:00
e2d7dcf0c2 chore(skills): prune bundled slides and spreadsheets docs 2026-03-10 14:11:27 -07:00
8bffa0c2b0 fix: restore just switch on current nixpkgs 2026-03-10 14:06:37 -07:00
77a71e0a10 Update taffybar and Emacs configuration 2026-03-09 00:46:12 -07:00
9d5f048f8c chore: update codex tooling and taffybar 2026-03-05 17:54:03 -08:00
a9a7546a1f chore(nixos): update flake lock inputs 2026-03-05 07:26:00 -08:00
5860600bd0 feat(nixos): add gws and fix switch blockers 2026-03-05 07:25:48 -08:00
b0ecf2a0d7 chore(taffybar): update input wiring and tray priorities 2026-03-05 07:24:58 -08:00
fb9a30b8a6 chore(agents): refresh metadata and skill creator loader 2026-03-05 07:24:26 -08:00
6e7a8f6f54 chore(git): scope org-agenda-api identity overrides 2026-03-05 07:24:08 -08:00
org-agenda-api
dc9c1d0de5 nixos: bump flake lock inputs 2026-03-03 11:52:00 -08:00
org-agenda-api
e2f69af4e5 taffybar: add CPU widget and advance local pin 2026-03-03 11:51:42 -08:00
org-agenda-api
988df4ba8d hyprland: add hypridle and hyprlock integration 2026-03-03 11:51:37 -08:00
org-agenda-api
42c7aeb9e3 gitconfig: add org-agenda-api identity and safe directories 2026-03-03 11:51:14 -08:00
org-agenda-api
8722cac7d0 docs(skill): expand disk-space cleanup playbook 2026-03-03 11:51:09 -08:00
org-agenda-api
965a2ff070 nixos: remove stale synergy patch override 2026-03-02 21:23:46 -08:00
37eb01bee2 Flake lock bump 2026-02-25 10:29:20 -08:00
728b71f484 Update flake locks for status-notifier-item KDE SNI fix
Update status-notifier-item to 0.3.2.10 which fixes KDE apps
(kdeconnect-indicator) not registering tray icons due to an overly
strict ownership check rejecting multi-connection SNI registration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 02:04:33 -08:00
dbcbf951f0 Prevent org from saving org-agenda-files to customize
org-agenda-files is managed programmatically. Override
org-store-new-agenda-file-list to use setq instead of
customize-save-variable, and reset org-agenda-files to nil
before building it so stale customize values are ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:01:46 -08:00
8fdd9433c8 Add recurring.org to org-agenda-files
Include ~/org/recurring.org in both Emacs and the org-agenda-api
container so recurring tasks appear in the agenda.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:23:22 -08:00
f5ac66ecb6 Add personal information to AGENTS.md 2026-02-23 11:34:11 -08:00
fc2eb80ee9 Update ACME/nginx org-agenda-api host config and stage taffybar repos 2026-02-23 01:02:14 -08:00
c76cd297c0 Fix org-agenda-api host 2026-02-22 23:54:56 -08:00
1826a77384 Another taffy bump 2026-02-21 19:30:44 -08:00
2d28a20948 taffybar bumps 2026-02-21 19:07:59 -08:00
3f5b126cbe Add disk space cleanup skill 2026-02-20 23:10:01 -08:00
f67638947a Update wallpaper source dir and fix README badge link 2026-02-20 08:08:11 -08:00
220f5b733c nixos: skip taffybar startup in KDE sessions 2026-02-20 06:07:00 -08:00
4f80e2d0bb Set taffybar position back to top 2026-02-20 06:05:51 -08:00
17a0b280cd gh-pages: make org export resilient in CI 2026-02-20 05:56:09 -08:00
899ec139e9 taffybar: refine tray icon priorities and icon lookup 2026-02-20 05:55:18 -08:00
68c92992a2 Update taffybar submodule (workspace fixes + v5.2.1) 2026-02-20 05:43:46 -08:00
89f6495c67 Track taffybar sni-priorities.yaml 2026-02-20 05:14:28 -08:00
272cfec5ee chore(taffybar): bump taffybar to 6cfc1f6 2026-02-20 05:04:02 -08:00
f4ce538df7 chore(taffybar): bump upstream input and refresh locks 2026-02-20 05:01:34 -08:00
8ea3adb49f chore(taffybar): bump upstream submodule to latest tray/workspace fixes 2026-02-20 04:22:31 -08:00
db1dd5e557 ignore(taffybar): ignore local SNI priority state files 2026-02-20 04:22:20 -08:00
85b9b2c8ad Use upstream prioritized collapsible SNI tray in local config 2026-02-20 03:57:58 -08:00
3a4fafc9d4 style(taffybar): align combined labels and keep clock centered 2026-02-20 02:43:15 -08:00
588087270f refactor(taffybar): unify workspace widgets across backends 2026-02-20 02:42:55 -08:00
3bf1d8dadb Fix a few imalison-taffybar issues 2026-02-20 01:50:57 -08:00
b432d758a4 chore(taffybar): align flake follows and refresh locks 2026-02-20 01:46:34 -08:00
77dcec7849 chore(nixos): simplify switch recipe aliases 2026-02-20 01:45:59 -08:00
2416f46a3c taffybar: Remove icons from date/time 2026-02-20 01:29:15 -08:00
3af98f96bc Disable railbird cache 2026-02-20 01:27:29 -08:00
6befc1c6ff Update taffybar submodule for wakeup debug widget 2026-02-20 01:15:14 -08:00
7ff9efe0f4 feat(sni): enable Flameshot grim adapter 2026-02-20 01:07:46 -08:00
97ac691f2c feat(config): use SSH git protocol with gh credential helper 2026-02-20 01:07:31 -08:00
a63f286bf2 feat(taffybar): add wakeup debug widget and safer hyprctl handling 2026-02-20 01:07:07 -08:00
5738a7b8ce Update taffybar submodule for wakeup manager fix 2026-02-20 00:52:51 -08:00
f1498ceb1e feat(clock): stack date above time 2026-02-20 00:52:07 -08:00
01d9d21a85 chore(nix): bump flake lock 2026-02-20 00:35:18 -08:00
e05832f5ca taffybar: add configurable persistent SNI tray priorities 2026-02-20 00:03:27 -08:00
5b1c0d972f docs(skill): define unsubscribe scan execution defaults 2026-02-20 00:00:00 -08:00
8db8c02304 feat(wm): add rofi wallpaper launcher and keybindings 2026-02-19 23:59:51 -08:00
adf320a414 nixos: bump keepbook and expose keepbook binaries 2026-02-19 23:08:23 -08:00
6c5d493561 nixos: drop invalid agenix.service ordering for tailscale autoconnect 2026-02-19 19:58:22 -08:00
aea078c17a Remove broken action-cache gitlink entries 2026-02-19 12:13:37 -08:00
196bf9155e Add emacs snippets and workspace stubs 2026-02-19 12:13:37 -08:00
6af998dfd8 Add Claude settings and cached GitHub actions 2026-02-19 12:13:37 -08:00
631c2cc09f Adjust NixOS config and tailscale setup 2026-02-19 12:13:37 -08:00
c01c96dde0 Update xmonad dev environment setup 2026-02-19 12:13:37 -08:00
a2eaa9c058 Update Tailscale auth key secret 2026-02-19 12:04:53 -08:00
32e594ab22 Clarify credential handling in AGENTS instructions 2026-02-18 22:08:18 -08:00
67f4381e06 Combine laptop battery/network and asus/disk widgets 2026-02-18 21:20:01 -08:00
89529e7f62 Sanitize MPRIS metadata newlines while keeping stacked label 2026-02-18 17:59:58 -08:00
6da3da52f8 nixos(keepbook): drop obsolete sync daemon patch 2026-02-18 12:53:33 -08:00
fc89fcee1a claude: skip dangerous mode permission prompt 2026-02-18 12:49:31 -08:00
06bbd5b7cd nixos/taffybar: pin status-notifier-item package 2026-02-18 12:49:25 -08:00
a7cab63fa7 nixos: add switch-local recipe 2026-02-18 12:48:13 -08:00
292ef25fcd nixos: bump keepbook flake input 2026-02-18 12:43:33 -08:00
8775e41cb3 Remove asciinema config from dotfiles 2026-02-18 12:31:28 -08:00
f314db999c Fix cachix-populate just recipe (shebang) 2026-02-18 11:54:03 -08:00
e3e23f9c7a railbird-sf: serve syncthing/docs via nginx 2026-02-18 11:46:28 -08:00
ee16ba6aa1 nixos: patch keepbook-sync-daemon for updated sync_all_if_stale 2026-02-18 10:59:20 -08:00
78e6dce5eb taffybar config: fix tray/battery order for packEnd 2026-02-18 10:54:55 -08:00
92c7774539 nixos: nix flake update 2026-02-18 10:36:21 -08:00
110bbda132 taffybar: fix parse error in laptop widget list 2026-02-18 10:34:33 -08:00
8b8d044290 Move sni tray back 2026-02-18 10:28:35 -08:00
0fde36b099 Fix cachix-populate just recipe 2026-02-18 10:11:58 -08:00
57a3e0ebe3 taffybar config: place battery after tray 2026-02-18 10:11:58 -08:00
0eb9a446b5 Add just commands to auth and populate Cachix 2026-02-18 10:07:00 -08:00
3c9ba043d0 taffybar config: drop barLevels; tray back on main row; restore sun/lock text 2026-02-18 09:56:45 -08:00
5b480040a0 CI: include taffybar Cachix substituter 2026-02-18 02:14:44 -08:00
9e2a978911 CI: free disk and pin substituters 2026-02-18 02:07:13 -08:00
9c1888f949 repo hygiene: move secrets to pass; add examples; misc updates 2026-02-18 01:37:46 -08:00
67c7290a43 Add Cachix cache and CI workflow 2026-02-18 01:17:30 -08:00
8bcb531e56 taffybar config: stack sun+lock widget 2026-02-18 01:17:30 -08:00
8eac8fb6f4 taffybar config: stacked RAM/SWAP, barLevels tray row, bump locks 2026-02-18 01:17:30 -08:00
67d1577ae8 chore(nixos): bump org-agenda-api (mova v5.20.3) 2026-02-17 18:51:43 -08:00
0a8d812ee8 chore(nixos): update playwright-cli override hash 2026-02-17 17:39:05 -08:00
5a9e3c4894 chore(nixos): point switch recipes at GitHub flake URL 2026-02-17 17:38:53 -08:00
0730e75088 feat(rumno): tune notification rules and timeout handling 2026-02-17 17:38:38 -08:00
df79ced549 feat(hyprland): patch hyprexpo with workspace numbers and bring mode 2026-02-17 17:38:17 -08:00
a50dbc60e8 nixos: remove retired user accounts and related config 2026-02-17 16:46:25 -08:00
5b12d0dc70 chore(secrets): rotate org-agenda-api auth password 2026-02-16 16:35:30 -08:00
c9884b2825 fix(org-agenda-api): import single-line secrets via flyctl 2026-02-16 16:35:19 -08:00
acb20132e3 feat(taffybar): split now-playing label and bump submodule 2026-02-16 16:35:03 -08:00
0ddc3b4a65 Update unsubscribe skill cleanup guidance 2026-02-16 12:23:17 -08:00
cd535b7b01 Rotate org-agenda-api prod auth password 2026-02-15 11:43:50 -08:00
4753901ef1 nixos: apply nixpkgs PR 490230 (playwright-cli) 2026-02-13 14:10:59 -08:00
8431dcc87d nixos: bump keepbook 2026-02-13 13:19:19 -08:00
5a5fda5cc0 nixos: add playwright-cli 2026-02-13 13:19:19 -08:00
2769eeb5a9 nixos: pin taffybar via github input (override locally when needed) 2026-02-13 11:50:26 -08:00
71736af86d nixos: bump git-sync-rs 2026-02-13 11:45:04 -08:00
d024712b78 nixos: lock taffybar flake input via git+file 2026-02-13 05:24:23 -08:00
f35e26aff1 nixos: update lock for taffybar path input 2026-02-13 05:19:02 -08:00
aee881dcd7 nixos: allow agenix to decrypt tailscale authkey via user ssh key 2026-02-13 04:38:14 -08:00
8dde61a230 nixos: tailscale auto-connect via agenix auth key 2026-02-13 04:35:47 -08:00
fa81dac17f nixos: bump kanshi-sni 2026-02-13 04:17:42 -08:00
87e10caa75 taffybar: collapse mpris when no visible children 2026-02-13 04:17:33 -08:00
808ad38753 nixos: fix oci-containers Restart conflict for org-agenda-api 2026-02-13 04:07:03 -08:00
2f3aa004f5 nixos: enable tailscale module 2026-02-13 03:57:41 -08:00
2162f058ff nixos(keepbook): remove removed keepbook-sync-daemon tray-icon arg 2026-02-13 03:51:57 -08:00
dbbbb7ff5a Enable flake nixConfig and add cache.railbird.ai substituter 2026-02-13 03:25:30 -08:00
5155c96195 chore(nixos): bump keepbook 2026-02-13 03:22:56 -08:00
bc64378bbc chore(nixos): bump git-sync-rs 2026-02-13 02:09:57 -08:00
26779e7011 nixquick: force-disable k3s 2026-02-13 01:28:49 -08:00
468b3b9ea9 Bump keepbook 2026-02-13 01:28:31 -08:00
e04ca29749 Add journaling skill 2026-02-13 01:12:52 -08:00
7499328179 Remove debug taffybar logging 2026-02-13 01:07:37 -08:00
50f167e5b2 nixos: update locks and rootless podman prune 2026-02-13 00:26:08 -08:00
1161cea530 hypr: treat rumno as overlay 2026-02-13 00:05:42 -08:00
43adf5a8fe taffybar: refresh lockfiles 2026-02-13 00:05:30 -08:00
4a6b178546 gitignore: ignore build artifacts and generated configs 2026-02-13 00:03:29 -08:00
93ddab4e9e nixos: quiet warn-dirty and scope nixified-ai import 2026-02-13 00:02:13 -08:00
25a07dd7b2 nixos: replace rcm with home-manager dotfile links 2026-02-12 23:45:49 -08:00
9dba5fbad3 Move taffybar back to top 2026-02-12 23:11:59 -08:00
867abf7cee taffybar: bottom bar + fix Hyprland workspace widget config 2026-02-12 22:57:15 -08:00
02abb5f8e4 chore(nixos): bump git-sync-rs to v0.7.0 2026-02-12 22:55:03 -08:00
b284ceb716 Add rofi launcher for tmux Codex and nixos agent notes 2026-02-12 22:33:14 -08:00
134009cebc nixos/taffybar: propagate status-notifier-item dbus fix from upstream master 2026-02-12 20:28:27 -08:00
1e28cb10ab Treat rbsf.tplinkdns.com as managed ssh host 2026-02-12 20:27:30 -08:00
eaba704fab flake bump 2026-02-12 20:25:38 -08:00
b1072f0528 chore: unify codex skills under agents 2026-02-12 20:23:16 -08:00
94172a65b7 feat: add logical-commits skill 2026-02-12 20:21:51 -08:00
a4e4ea3a94 Add agent project constellation guides and link policy 2026-02-12 20:21:37 -08:00
2479c98476 Propagate taffybar lock updates into nixos flakes 2026-02-12 20:09:14 -08:00
955216bff7 Propagate status-notifier-item update into imalison-taffybar 2026-02-12 20:09:10 -08:00
cca3383674 Enable all terminfo entries for system 2026-02-12 20:08:08 -08:00
3fe67188e3 gitignore: ignore local status-notifier-item checkout 2026-02-12 19:14:55 -08:00
f25d51c92d dotfiles: bump taffybar submodule 2026-02-12 19:14:48 -08:00
ee17a8756f nixos: refresh flake lock 2026-02-12 19:14:21 -08:00
18168dbc59 dotfiles: add rofi agentic skill launcher binding 2026-02-12 19:14:19 -08:00
d6cedef14d nixos: fix switch on strixi-minaj nvidia build 2026-02-12 19:10:40 -08:00
fe6d6fbcb8 nixos: disable status-notifier-item checks in taffybar overlay 2026-02-12 16:42:45 -08:00
93105993c6 nixos: add SSH TERM compatibility wrapper and ghostty terminfo 2026-02-12 16:42:45 -08:00
30c4d55407 emacs: guard org-mode yasnippet disable hook 2026-02-12 16:42:45 -08:00
b8da65b2fd hyprland: tune window animations and restart hyprscratch 2026-02-12 16:42:45 -08:00
5f2c84b645 codex: add hyprland trust and OpenAI docs MCP server 2026-02-12 16:42:45 -08:00
463c8ff36b flake: update inputs and lockfile sources 2026-02-12 16:30:03 -08:00
09ac9fa3d5 nixquick: disable railbird-k3s 2026-02-12 16:27:44 -08:00
6395685ae9 nixos: adjust desktop package selection 2026-02-12 16:27:44 -08:00
d7efe5b6a6 nixos: set NetworkManager rc-manager to symlink 2026-02-12 16:27:44 -08:00
c36d930eb5 nixos: bump keepbook flake input 2026-02-12 14:19:27 -08:00
616769fe60 nixos: remove taffybar ecosystem follows and fix bitwarden rename
Remove top-level gtk-sni-tray, gtk-strut, status-notifier-item,
dbus-menu, and dbus-hslogger inputs that only existed as follows
targets. Let taffybar and imalison-taffybar resolve their own
ecosystem deps, eliminating cascading lock update headaches.

Also rename bitwarden -> bitwarden-desktop in kat.nix (nixpkgs rename).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:04:50 -08:00
7d099a2be3 Add email unsubscribe skill 2026-02-12 12:52:29 -08:00
096d3f543a agents: Add worktrees instructions 2026-02-12 11:18:50 -08:00
266b142d34 Update SNI tray inputs
Bump flake.lock pins for gtk-sni-tray and status-notifier-item to pick up watcher-restart dedupe and nix-friendly test bus config.
2026-02-12 07:07:03 -08:00
97f65425dc nixos: enable keepbook sync 2026-02-12 01:26:15 -08:00
4f29a0d48d org-agenda-api: guard yas disable hook in container config 2026-02-11 19:05:13 -08:00
f9dbe7fb54 nixos: bump org-agenda-api input to v4.4.1 2026-02-11 18:53:15 -08:00
f0c8c61732 nixos: propagate status-notifier-item v0.3.2.3 2026-02-11 15:24:01 -08:00
63462694f5 taffybar: propagate status-notifier-item v0.3.2.3 2026-02-11 15:23:06 -08:00
da28a0bd05 flake: bump status-notifier-item to v0.3.2.2 2026-02-11 14:37:57 -08:00
52def9b43d nixos: update status-notifier-item input to v0.3.2.2 2026-02-11 14:33:55 -08:00
eea3683aa7 podman: Enable daily auto-prune with --all flag
Without --all, only dangling (untagged) images were pruned,
allowing tagged-but-unused CI images to accumulate (~223G).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 01:39:06 -08:00
a91f58bf82 Update tmux titling guidance in AGENTS 2026-02-11 00:32:16 -08:00
1c7b6437ce Ignore local .worktrees directory 2026-02-11 00:32:12 -08:00
66a01a7b39 Ignore generated hyprscratch.conf symlink 2026-02-10 23:58:51 -08:00
fa2af8f000 taffybar: Reorder widgets 2026-02-10 23:51:56 -08:00
d3ea72b08d nixos: fix switch space issues and set hourly upgrades 2026-02-10 23:46:07 -08:00
f93f2c5d3e Make railbird-sf update hourly 2026-02-10 22:57:36 -08:00
0228989600 Bump flake.lock 2026-02-10 22:56:59 -08:00
280cf5f7ff Don't save plans 2026-02-10 22:54:04 -08:00
b1de4e57eb docs: add hyprscratch migration plan 2026-02-10 22:43:00 -08:00
67708f4f7f codex: trust notifications-tray-icon project path 2026-02-10 22:42:56 -08:00
531a10e1d8 nixos: fix org icon path and disable conflicting power services 2026-02-10 22:42:53 -08:00
53ab4688da emacs: retangle org config when any generated file is stale 2026-02-10 22:42:50 -08:00
5ca733194a taffybar: refine tray behavior and add SNI menu debug tooling 2026-02-10 22:42:47 -08:00
f9a0a5c9c0 taffybar: align flake inputs and drop local overlay patch 2026-02-10 22:42:43 -08:00
2180c042de Tighten taffybar widget spacing 2026-02-10 22:34:01 -08:00
cf59eddef9 nixos/sni: add kanshi-sni tray service 2026-02-10 22:28:23 -08:00
d8ff8a939a feat: add hyprscratch scratchpad management with rule reapply fork
- Add hyprscratch from colonelpanic8/reapply-rules-on-toggle fork that
  reapplies size/position/float rules on every toggle (not just spawn)
- Configure 9 scratchpads: htop, volume, spotify, element, slack,
  transmission, dropdown, gmail, messages
- Use clean mode (hide on workspace change) and auto-dismiss (only
  dropdown persists)
- Fix pavucontrol class (org.pulseaudio.pavucontrol) and dropdown
  position (offset by taffybar height)
- Add kanshi-sni, dbus-menu, dbus-hslogger flake inputs and follows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 22:28:01 -08:00
8efd44f69e Make completion matching case-insensitive by default 2026-02-10 21:52:37 -08:00
7e397bd440 chore: bump git-sync-rs and notifications-tray-icon flake inputs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:09:30 -08:00
1b726532c1 refactor: split taffybar skill into ecosystem release and NixOS flake chain
Separate the taffybar ecosystem release workflow (Hackage publishing,
dependency graph, version bounds) from the NixOS flake chain integration
(three-layer flake.lock cascade, bottom-up update strategy). Each skill
cross-references the other.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:25:00 -08:00
ecbe245a6d feat: propagate module enables (hyprland/xmonad -> taffybar -> sni)
Move enable propagation into the modules themselves instead of
desktop.nix. Relax the assertion to only prevent both taffybar
and waybar from being enabled simultaneously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:17:46 -08:00
3010260cd1 docs: note manual doc upload requirement for GTK-dependent packages
Hackage can't build docs for packages with GTK/GI system deps. This
affects taffybar, gtk-sni-tray, gtk-strut, and dbus-menu — docs must
be built locally and uploaded manually for those.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:16:28 -08:00
741a72ebd9 refactor: separate ecosystem release from NixOS config in taffybar skill
Split the skill into two clear concerns: the taffybar org package
release workflow (dependency graph, Hackage publishing, version bounds)
and the personal NixOS flake chain integration. The NixOS section
explains the three-layer flake.lock cascade and why bottom-up updates
matter, without being prescriptive about always doing all layers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:15:17 -08:00
f8a3273643 feat: remove katnivan git-sync, add per-repo tray icons
Remove katnivan repository from both imalison and kat git-sync configs.
Add per-repo tray icon support with icon mapping for org and password-store.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:49:29 -08:00
80926700c1 fix: remove muted color override on mpris label
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:46:11 -08:00
781b5297ad feat: reorganize skills with canonical agents/ locations and symlinks
- Symlink dotfiles/claude/CLAUDE.md -> ../agents/AGENTS.md
- Move global skills to dotfiles/agents/skills/ as canonical location
  (hackage-release, org-agenda-api-production, release)
- Add new taffybar-ecosystem-release skill documenting the taffybar
  package dependency graph and release propagation workflow
- ~/.claude/skills/ entries are now symlinks to canonical locations
- email-cleanup and weekly-scheduling moved to ~/org/agents/skills/
- Removed stale debug-video-processing symlink

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:40:52 -08:00
73c766e828 feat: rename passgen to xkcdpassgen, fix arg passing, add number and char class controls
Fix first-invocation failure by passing "$@" through to the function.
Add number to default password suffix and allow opt-out of each
character class (-U uppercase, -N number, -S symbol).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:25:13 -08:00
6f16b36faa feat: add wlsunset home-manager module for Wayland night light
Replace the manual wlsunset package + commented exec-once with a proper
home-manager services.wlsunset module tied to hyprland-session.target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 18:38:03 -08:00
aa286e6855 agents: add credential retrieval instructions using pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 17:38:08 -08:00
59f7f35aa0 feat: add ASUS platform profile widget and fix flake deps
- Add ASUS widget to laptop bar showing profile icon, CPU freq, and temp
- Add dbus-menu and dbus-hslogger flake inputs to fix gtk-sni-tray build
- Simplify CSS color rules for end-widget pills
- Update taffybar submodule with ASUS Information/Widget modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 15:26:21 -08:00
55b9724b92 feat: add screenLock and wlsunset widgets to taffybar config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:43:56 -08:00
10019a13f2 taffybar: remove accidentally committed debug code
Remove debugPopupSNIMenuHook, withDebugServer, and associated debug
imports/deps that were accidentally included in 8d6664d8.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:34:19 -08:00
5b69d613b8 justfile: add remote-switch recipe for remote NixOS rebuilds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 21:15:50 -08:00
67b92178a6 flake: use direct commit URL for happy-coder patch
Moves the happy-coder patch from PR-based template to custom patches
using a stable commit URL instead of the PR number.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 21:15:49 -08:00
c5f3de9bd9 [NixOS] Disable k3s and enable autoUpgrade on jimi-hendnix 2026-02-09 21:10:58 -08:00
8d6664d83b nix flake update 2026-02-09 19:17:24 -08:00
655b0c6f2d nixquick: enable hourly auto-upgrade
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:15:39 -08:00
950149769d nix: wrap git-sync-rs with convenience symlinks
Adds git-sync and git-sync-on-inotify as symlinks to git-sync-rs binary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:15:36 -08:00
e97b838cda git-sync: remove config repository
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:15:33 -08:00
913767bec7 hyprland: add empty monitors and workspaces config files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:15:30 -08:00
d7a748177b taffybar CSS: remove inline menu/popover styling
The narrowed :not(menu):not(menuitem):not(popover):not(window) selectors
now prevent bar styles from bleeding into popup menus, making the
explicit menu overrides unnecessary. Menus inherit clean styling from
the GTK theme instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:42:01 -08:00
86d667c5e1 taffybar CSS: extract per-widget colors to nth-child rotation
Replace individual .outer-pad.audio, .outer-pad.network, etc. color
rules with a 5-color palette in end-widget-colors.css that cycles via
:nth-child(5n+N). Add workspace pill reset to prevent the rotation
from bleeding into workspace widgets. Move per-widget color variables
from theme.css into end-widget-colors.css (keep tray colors in theme
since the SNI tray is a center widget outside the rotation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:41:33 -08:00
0fb5bc132a taffybar CSS: narrow selectors to prevent menu/popover color bleed
Use :not(menu):not(menuitem):not(popover):not(window) guards on all
wildcard selectors so bar typography and background-color rules don't
bleed into SNI popup menus and popovers attached via menuAttachToWidget.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:40:43 -08:00
67d5bd793b taffybar: reduce SNI tray overlay icon size (2/5 -> 1/3)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:37:22 -08:00
0a6931cf77 flake.lock: update inputs and remove stale HLS-related locks
Update home-manager, NixOS-WSL, status-notifier-item, and
notifications-tray-icon. Remove lock entries for the old
haskell-language-server input and its transitive dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:49:23 -08:00
63b99e4d67 notifications-tray-icon: add dedicated module and update flake input
Add notifications-tray-icon.nix module with overlay and home-manager
service config. Update flake input to colonelpanic8 fork and remove
stale HLS input follows. Clean up xmonad.nix by removing the old
overlay reference and commented-out service definition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:49:18 -08:00
a8aefe3d5e hyprland: replace services.kanshi with package, add nwg-displays
services.kanshi is a Home Manager option, not a NixOS module option,
so the NixOS build was failing. Install kanshi as a package instead.
Also add nwg-displays for GUI monitor arrangement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:49:12 -08:00
01e84f24ba git-sync: enable tray indicator via GIT_SYNC_TRAY env var
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:33:31 -08:00
09b4ef28f8 Replace CLAUDE.md symlink with standalone file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:33:08 -08:00
4add566e8c flake: add git-sync-rs as top-level input and deduplicate
Move git-sync-rs from a nixpkgs PR patch to a direct flake input,
add overlay and package, and have org-agenda-api follow it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:33:05 -08:00
eb707d3168 taffybar: fix popup menu styling and submenu color bleed
Use :not(menu):not(menuitem):not(popover) selectors to prevent
forcing transparent backgrounds on popup menus attached via
menuAttachToWidget. Add dedicated .dbusmenu-submenu styling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:33:00 -08:00
2615253a7a hyprland: add ghostty dropdown scratchpad and specialWorkspace animation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:32:54 -08:00
284ae929a7 Move dunst config from xmonad.nix to desktop.nix
Dunst works on both X11 and Wayland, so it belongs in the shared
desktop config rather than under the xmonad-specific module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:13:10 -08:00
c170442904 Add IMALISON_SESSION_TYPE=x11 condition to picom and autorandr
Both are X11-only services that shouldn't start in Wayland sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:12:25 -08:00
157741a93c Set IMALISON_SESSION_TYPE env var for reliable X11/Wayland detection
systemd user environment persists across login sessions, so
XDG_SESSION_TYPE can be stale. Proactively set a custom variable
on each session start so ConditionEnvironment checks are reliable.

Use it for xsettingsd and random-background (X11-only services).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:05:48 -08:00
e9545219b3 Enable daily auto-upgrade on strixi-minaj, railbird-sf, and ryzen-shine
Set the autoUpgrade flake reference globally in configuration.nix so
machines only need to opt in with system.autoUpgrade.enable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:41:59 -08:00
a1c31dcfc4 taffybar: fix SNI popup menu styling (white bg, black text, blue hover)
GTK's menuAttachToWidget makes popup menus CSS descendants of the tray
widget, so .outer-pad.sni-tray * (specificity 0,2,0) was bleeding light
tray text colors into menu items.  Fix by using the same parent selectors
with menu descendant types (.outer-pad.sni-tray menu menuitem *) for
specificity 0,2,2+ that definitively overrides the tray color rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:35:32 -08:00
4d014978d2 fix: disable use-package :ensure in container config
The tangled org-config now includes the org-window-habit use-package
block (added in efc50ec1) which uses elpaca-style :ensure recipes.
Standard use-package cannot parse these. Override the normalizer to
accept and ignore :ensure since all packages are provided via Nix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:29:17 -08:00
3123aa6c3f flake: deduplicate inputs with follows
Add follows declarations to reduce duplicate dependency copies:
- nixpkgs: 16 → 8 copies
- flake-utils: 9 → 1 copy
- systems: 12 → 2 copies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:16:17 -08:00
c12f56ff86 chore: bump org-agenda-api (mova 5.20.0)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:13:48 -08:00
7b52fb1f4b Update flake inputs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:58:45 -08:00
111afc60c0 Add happy-coder package
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:58:42 -08:00
df9b5e61c9 taffybar: add SNI menu, withLogLevels, and clean up deps
- Wrap network widget with withNmAppletMenu for click-to-open menu
- Use withLogLevels hook instead of manual enableLogger
- Remove unused aeson, directory, yaml dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:58:40 -08:00
bdcba389cc hyprland: switch htop scratchpad terminal to alacritty
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:58:35 -08:00
ba6baa8628 Fix deprecated xorg package references
Rename xorg.{libXdmcp,libXtst,xev,xwininfo} to their new top-level names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:38:27 -08:00
ddaf752a68 taffybar: change default log level from INFO to WARNING
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:16:16 -08:00
a8cb334938 taffybar: add yaml/aeson deps and log-levels config file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:16:11 -08:00
c7da4f35ce Bump taffybar submodule and update flake.lock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:16:08 -08:00
9fc1ddc885 Update taffybar (ScalingImage refactor), add toggle keybinding
- Bump taffybar submodule with ScalingImage refactor replacing autoSizeImage
- Add hyper+slash keybinding for toggle_taffybar
- Simplify mprisWidget wrapper in taffybar.hs
- Update flake.locks for gtk-sni-tray
- Add codex trust for taffybar submodule
- Add waybar widget ideas notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:19:00 -08:00
fc45f40b97 chore: bump taffybar submodule (gtk-sni-tray 0.1.11.2)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 11:04:11 -08:00
615a265def taffybar: bump gtk-sni-tray and update widget layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 00:38:02 -08:00
ede5ccf540 Fix tmclaude to avoid sandboxing 2026-02-08 00:00:43 -08:00
8b888f02a5 USe ghosttty 2026-02-08 00:00:28 -08:00
1e8a591b4f taffybar: accent-tinted active workspace, white border active window
Use accent color (#f1b2b2) tint for the active workspace pill instead of
a white outline, so it's visually distinct from the active window
highlight. Add white border and background to the active window icon
container within workspaces. Also add nerd font family to mpris icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:50:28 -08:00
4c89d204d5 taffybar: add nerd font icon to mpris widget
Wrap the mpris grid in a box with a nerd font music note icon (U+F075A)
so it follows the same icon+label pattern as other widgets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:50:22 -08:00
bf2ec862a9 taffybar: bump font size to 11pt and add icon-label spacing
Increase global font size from 9pt to 11pt for better readability.
Add padding-right on icon-label icon elements to prevent nerd font
glyphs from overlapping adjacent text. Consolidate battery CSS
selectors to match the new single-widget structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:33:52 -08:00
f80340fee7 taffybar: use nerd font icon+label pairs for battery and disk widgets
Replace the separate batteryIconWidget + batteryTextWidget with a single
batteryWidget using batteryTextIconNew paired with textBatteryNew via
buildIconLabelBox. Switch diskUsageWidget from diskUsageLabelNew to
diskUsageNew which includes a nerd font disk icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:33:48 -08:00
de89725abb chore: bump taffybar submodule (gtk-sni-tray 0.1.10.3)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 22:11:04 -08:00
479bea8ca5 chore: bump taffybar submodule (gtk-sni-tray update)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 21:18:59 -08:00
6b1d25cdc6 chore: update user config to use new icon-label widget variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 21:04:40 -08:00
24fe3fbb6b taffybar: add comments explaining recent CSS additions
Document the @import url() requirement, per-widget color variables,
workspace label positioning via padding (not margin, which GTK
overlays ignore), asymmetric workspace padding, active workspace
outline targeting, and bar border-radius.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:49:32 -08:00
c7a0ddcc62 taffybar: add white outline to active workspace squircle
Target .workspaces .active .outer-pad with an inset box-shadow to
highlight the currently focused workspace pill.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:47:19 -08:00
89add17016 taffybar: lighter bar background and rounded corners
Reduce bar alpha from 0.55 to 0.35 for more transparency and add
6px border-radius to match widget squircles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:20:04 -08:00
6a388676df taffybar: reduce right-side padding on workspace pills
Asymmetric inner-pad padding (10px left, 3px right) so workspace
number labels have room on the left without extra space on the right.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:18:13 -08:00
319ae7ba6f taffybar: reduce widget squircle border-radius for squarer shape
outer-pad: 12px -> 6px, inner-pad: 9px -> 4px

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:16:48 -08:00
9fcb5c073c taffybar: position workspace number labels inside squircle pills
Use padding on the workspace-label (not margin on the overlay-box,
which GTK overlays ignore) to inset the number into the pill area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 20:14:41 -08:00
8ae1b2d906 taffybar: add distinct colored squircle backgrounds per widget
Fix CSS import syntax (bare `import` -> `@import url()`) so
@define-color variables from theme.css are available. Define
per-widget background/foreground/border colors and add CSS rules
for clock, disk-usage, sni-tray, battery, and backlight widgets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 19:23:45 -08:00
781a64aa92 hyprland: add rofi icons to window go/bring/replace pickers
Add desktop-entry icon lookup to the rofi window picker scripts,
matching the XMonad setup's icon support. A shared helper script
(window-icon-map.sh) builds a class→icon mapping from .desktop files
and each picker uses rofi's dmenu icon protocol (\0icon\x1f).

Also replaces the X11-only "rofi -show window" with a native
Hyprland window picker using hyprctl clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:08:12 -08:00
f766fbc8dc Replace codex_tmux with generic trw, tmclaude, tmcodex helpers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:39:21 -08:00
986e414570 nixos: load module-dbus-protocol for PulseAudio DBus support
The taffybar audio widget requires PulseAudio's DBus interface
(module-dbus-protocol) to read volume/mute state. Without it the
widget shows "n/a".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:39:04 -08:00
e4cb9e4861 taffybar: use library detectBackend for wayland/hyprland discovery
Remove detectBackendRobust and discovery helpers from taffybar.hs now
that the equivalent logic lives in System.Taffybar.Context.Backend
(taffybar PR #625). Update submodule and flake.lock accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:14:20 -08:00
9b5d515acc nixos: update flake.lock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:03:09 -08:00
151ee9b4c5 taffybar: update submodule and flake.lock
Bump taffybar submodule, gtk-sni-tray, status-notifier-item, xmonad,
and nixpkgs inputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:03:05 -08:00
b8030e39e0 taffybar: add AGENTS.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:02:59 -08:00
8a6081e736 taffybar: add taffybar-crop-bar utility
Shell script that crops the top bar region from a screenshot using
ffmpeg, auto-detecting height from Hyprland's reserved area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:02:55 -08:00
043d858289 taffybar: use button and overlay controllers for workspace widget
Wire up hyprlandBuildButtonController and
hyprlandBuildCustomOverlayController so workspace buttons are
clickable and the overlay layout is explicitly configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:02:50 -08:00
72cd82be13 taffybar: robust Wayland/Hyprland environment discovery
Instead of relying solely on environment variables (which can be stale
from systemd --user), actively discover wayland sockets and Hyprland
instance signatures from XDG_RUNTIME_DIR.  Fix up the process
environment so taffybar's internal backend detection agrees, and also
correct XDG_SESSION_TYPE in both directions.  Add INFO-level logging
for backend selection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:02:42 -08:00
8c8fbe774c taffybar: add theme.css and update CSS styling
Extract color variables into a dedicated theme.css and import it from
the main stylesheet.  Remove the bar gradient in favor of a flat
background, adjust workspace overlay-box margins, add SNI tray
double-padding fix, and clean up whitespace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:02:18 -08:00
ece1c8eac8 remove ivanm-dfinity-razer and uber-loaner host references
Both hosts are long dead. Removes their CSS files, taffybar host
config entries, and synergy aliases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:41:29 -08:00
eff7ff726f ci: fix gh-pages build by setting user-emacs-directory
The org-config macro reads preface/custom/config/bind .el files from
user-emacs-directory at macro-expansion time. In CI this defaulted to
~/.emacs.d/ where those files don't exist, causing the build to fail.
Point it at the repo's emacs.d directory instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:22:03 -08:00
482d0446d5 remove xremap from NixOS config
keyd handles all key remapping now, so xremap is no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 14:21:31 -08:00
efc50ec104 emacs: add missing tangle directive for org-window-habit
The org-window-habit use-package block had no :tangle header, so it
was never written to org-config-config.el. This meant the mode was
never activated and the advice on org-habit-parse-todo was never
installed, causing errors for habits without a scheduled repeater.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 13:24:48 -08:00
d97026eb6c emacs: fix transient void-variable on pgtk builds
The pgtk Emacs build lacks HAVE_TEXT_CONVERSION so
overriding-text-conversion-style is void, but transient's .elc
compiled on X11 has static-if expanded to reference it directly.
Define the variable before transient loads when it's missing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 13:24:43 -08:00
38e7b8c3eb hypr: improve minimize/unminimize workflow 2026-02-07 00:26:29 -08:00
b94c561c9f taffybar: restore SNI tray; name wayland widgets hyprland 2026-02-06 14:24:45 -08:00
42fa28f155 flakes: switch codex-cli-nix back to sadjow/main 2026-02-06 13:05:26 -08:00
558f2b9b0d agents: encourage nix run/shell for ad-hoc tools 2026-02-05 23:19:20 -08:00
1af5b5563b taffybar: fix Hyprland context deadlock 2026-02-05 22:22:28 -08:00
782ac9fbd0 nixos: unify wallpapers via syncthing
Start hyprpaper via a user service and set wallpaper via IPC on session start.\nPoint xmonad random-background at the same Syncthing wallpaper directory (X11 only).
2026-02-05 18:42:20 -08:00
ed752f37c5 nixos: add emacs-auto wrapper + desktop files 2026-02-05 18:42:20 -08:00
6d3d0c56d2 xmonad: bind Hyper keys via Ctrl+Alt+Super chord 2026-02-05 18:42:20 -08:00
fc887b5526 taffybar: avoid Wayland backend with stale systemd env
When taffybar is started via systemd --user, the manager environment can keep WAYLAND_DISPLAY/HYPRLAND_INSTANCE_SIGNATURE from an older Wayland session even when we're currently running an X11 xmonad session.

This causes taffybar's backend detection to select the Wayland backend, which prevents X11 struts from being applied and leads to incorrect placement.

Add a small wrapper backend detector that checks for an actual wayland socket and, if missing, sanitizes the env so taffybar's internal context uses the X11 backend.
2026-02-05 18:42:20 -08:00
76495da0b7 taffybar: menu css debugging tweaks 2026-02-05 18:42:20 -08:00
3a4aac3797 Update codex input and switch command 2026-02-05 14:42:02 -08:00
36d421876f flake: update inputs
Updated nixpkgs, home-manager, nix, and related inputs after running nix flake update.
2026-02-05 12:11:28 -08:00
9a8c158e03 strixi-minaj: stop managing waybar disks file
Avoid home-manager overwriting the repo-tracked waybar disks list when ~/.config is symlinked into this repo.
2026-02-05 12:11:18 -08:00
02595898e9 taffybar: bump submodule for new widgets
Track the local taffybar submodule commit that adds NetworkManager-backed widgets.
2026-02-05 12:04:14 -08:00
2f620dfff3 nixos: add chrome devtools desktop entry
- Add a google-chrome launcher with remote debugging enabled
- Add a tmux attach shell alias
2026-02-05 12:03:24 -08:00
5641d31580 hyprland: cap workspaces and add empty-workspace helpers
- Introduce HYPR_MAX_WORKSPACE (default 9) and enforce it in scripts
- Replace 'workspace empty' bindings with scripts that pick an empty id
- Add scroll-to-workspace helper for mouse wheel binds
2026-02-05 12:03:16 -08:00
bfdbaa7752 codex: bump default model
Switch to gpt-5.3-codex.
2026-02-05 12:01:33 -08:00
29f5f6baa4 taffybar: refresh config and add helpers
- Remove stack config in favor of cabal/flake
- Add helper scripts for running/restarting and screenshots
- Update bar CSS/HS config
2026-02-05 12:01:30 -08:00
b70800da59 waybar: add nowplaying module
- Add a playerctl-backed now playing widget
- Track a default disk list file instead of a home-manager symlink
2026-02-05 12:01:06 -08:00
3f9ac8edb9 nixos: change home-manager backup extension
Use 'hm-backup' globally and remove the per-user override.
2026-02-05 12:00:55 -08:00
a7e409f826 nixos: add quickshell/waybar/taffybar modules
- Switch taffybar input to the local submodule
- Add caelestia quickshell (home-manager module)
- Make waybar/taffybar mutually exclusive, defaulting based on xmonad
- Move tray ordering and status notifier watcher config into the right modules
2026-02-05 12:00:46 -08:00
c43fbe23d7 Hyprland: share workspace CSS helpers 2026-02-05 00:41:43 -08:00
a7e70c75e3 Hyprland: reuse workspace widgets 2026-02-05 00:34:57 -08:00
1a10851887 Hyprland: default special workspace filter 2026-02-05 00:27:10 -08:00
9e5e5f091c taffybar: use submodule and improve hyprland config 2026-02-05 00:12:51 -08:00
f476c6a97f hyprland: move cursor with moved window 2026-02-04 22:08:57 -08:00
e3e49d51f4 Fix gather-class to move all windows 2026-02-04 21:32:55 -08:00
b2a6695690 Add codex_tmux_resume helper 2026-02-04 17:15:51 -08:00
f731bb312a Manage mimeapps via home-manager 2026-02-04 17:15:27 -08:00
03bf952815 waybar: tweak sizing defaults 2026-02-04 16:37:45 -08:00
b1d16ea86d waybar: center widgets above bottom line 2026-02-04 16:23:55 -08:00
5d1b538579 Remove legacy XKB config 2026-02-04 16:12:20 -08:00
6d9cddbaeb nixos: split SNI tray services from xmonad 2026-02-04 16:12:20 -08:00
dfbd444649 nix-darwin: add claude-code and codex via dedicated flakes
Add flake inputs for codex-cli-nix and claude-code-nix with cachix
caching for pre-built binaries, matching the NixOS setup.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:09:52 -08:00
e5b77ae168 railbird-sf: enable k3s-single-node module
Switch from the multi-node railbird-k3s agent setup to the new
single-node k3s module with integrated GPU/CDI support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:57:02 -08:00
c307821b8d Add k3s-single-node module with GPU/CDI support
Provides a NixOS module for running a single-node k3s cluster with
NVIDIA GPU support via CDI (Container Device Interface). Includes
automatic deployment of the generic-cdi-plugin DaemonSet for GPU
resource allocation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:57:02 -08:00
d68a27bf88 Use custom waybar fork 2026-02-04 14:56:27 -08:00
47786404a2 keyd: add MoErgo hyper mapping 2026-02-04 13:20:34 -08:00
03d86c452a docs: note nixos flake + just switch 2026-02-04 13:15:15 -08:00
278 changed files with 19749 additions and 4281 deletions

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(rg:*)",
"Bash(wmctrl:*)",
"Bash(grep:*)",
"Bash(hyprctl:*)"
],
"deny": []
}
}

95
.github/workflows/cachix.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Build and Push Cachix (NixOS)
on:
push:
branches: [master]
paths:
- "nixos/**"
- "org-agenda-api/**"
- ".github/workflows/cachix.yml"
pull_request:
branches: [master]
paths:
- "nixos/**"
- "org-agenda-api/**"
- ".github/workflows/cachix.yml"
workflow_dispatch: {}
jobs:
nixos-strixi-minaj:
runs-on: ubuntu-latest
permissions:
contents: read
env:
# Avoid flaky/stalled CI due to unreachable substituters referenced in flake config
# (e.g. LAN caches). We keep this list explicit for CI reliability.
NIX_CONFIG: |
experimental-features = nix-command flakes
connect-timeout = 5
substituters = https://cache.nixos.org https://colonelpanic8-dotfiles.cachix.org https://org-agenda-api.cachix.org https://taffybar.cachix.org https://codex-cli.cachix.org https://claude-code.cachix.org
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ= org-agenda-api.cachix.org-1:liKFemKkOLV/rJt2txDNcpDjRsqLuBneBjkSw/UVXKA= taffybar.cachix.org-1:beZotJ1nVEsAnJxa3lWn0zwzZM7oeXmGh4ADRpHeeIo= codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing= claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk=
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Free disk space
run: |
set -euxo pipefail
df -h
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /usr/local/share/boost || true
sudo apt-get clean || true
df -h
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v16
- name: Use GitHub Actions Cache for /nix/store
uses: DeterminateSystems/magic-nix-cache-action@v7
- name: Require Cachix config (push only)
if: github.event_name == 'push'
env:
CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
run: |
set -euo pipefail
if [ -z "${CACHIX_CACHE_NAME:-}" ]; then
echo "Missing repo variable CACHIX_CACHE_NAME (Settings -> Secrets and variables -> Actions -> Variables)." >&2
exit 1
fi
if [ -z "${CACHIX_AUTH_TOKEN:-}" ]; then
echo "Missing repo secret CACHIX_AUTH_TOKEN (Settings -> Secrets and variables -> Actions -> Secrets)." >&2
exit 1
fi
- name: Setup Cachix (push)
if: github.event_name == 'push'
uses: cachix/cachix-action@v15
with:
name: ${{ vars.CACHIX_CACHE_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
skipPush: false
- name: Setup Cachix (PR, no push)
if: github.event_name == 'pull_request' && vars.CACHIX_CACHE_NAME != ''
uses: cachix/cachix-action@v15
with:
name: ${{ vars.CACHIX_CACHE_NAME }}
skipPush: true
- name: Build NixOS system (strixi-minaj)
run: |
set -euxo pipefail
nix build \
--no-link \
--print-build-logs \
./nixos#nixosConfigurations.strixi-minaj.config.system.build.toplevel \
--override-input railbird-secrets ./nixos/ci/railbird-secrets-stub

25
.gitignore vendored
View File

@@ -21,8 +21,33 @@
gotools
/dotfiles/config/xmonad/result
/dotfiles/config/taffybar/result
/nix-darwin/result
/nixos/result
/dotfiles/emacs.d/*.sqlite
/dotfiles/config/gtk-3.0/colors.css
/dotfiles/config/gtk-3.0/settings.ini
/dotfiles/emacs.d/.cache/
/dotfiles/emacs.d/projectile.cache
/dotfiles/emacs.d/projectile-bookmarks.eld
/dotfiles/config/fontconfig/conf.d/10-hm-fonts.conf
/dotfiles/config/fontconfig/conf.d/52-hm-default-fonts.conf
/dotfiles/config/taffybar/_scratch/
/dotfiles/config/taffybar/taffybar-*/
/dotfiles/config/taffybar/status-notifier-item/
/dotfiles/config/taffybar/.direnv/
/dotfiles/config/taffybar/dist-newstyle/
/dotfiles/config/taffybar/sni-priorities.dat
/dotfiles/config/xmonad/dist-newstyle/
/dotfiles/config/hypr/hyprscratch.conf
/.worktrees/
/result
# Secrets and machine-local state (managed via agenix/pass instead of git)
/dotfiles/config/asciinema/config
/dotfiles/config/remmina/remmina.pref
/dotfiles/config/screencloud/ScreenCloud.conf
# Local tool state
/.playwright-cli/
/nixos/action-cache-dir/
/dotfiles/config/taffybar/dbus-menu/

37
docs/cachix.md Normal file
View File

@@ -0,0 +1,37 @@
# Cachix for this repo
This repo's NixOS flake lives under `nixos/`.
The workflow in `.github/workflows/cachix.yml` can build the `strixi-minaj`
system closure on GitHub Actions and push the results to a Cachix cache.
## One-time setup
1. Create a Cachix cache (on cachix.org).
2. Create a Cachix auth token with write access to that cache.
3. In the GitHub repo settings:
- Add a repo variable `CACHIX_CACHE_NAME` (the cache name).
- Add a repo secret `CACHIX_AUTH_TOKEN` (the write token).
After that, pushes to `master` will populate the cache.
## Using the cache locally
Option A: ad-hoc (non-declarative)
```sh
cachix use <your-cache-name>
```
Option B: declarative via flake `nixConfig` (recommended for NixOS)
1. Get the cache public key from the Cachix UI:
- Open `https://app.cachix.org/cache/<your-cache-name>#pull`
- Copy the `Public Key` value shown there.
2. Add it to `nixos/flake.nix` under `nixConfig.extra-substituters` and
`nixConfig.extra-trusted-public-keys`.
Note: `nixos/nix.nix` sets `nix.settings.accept-flake-config = true`, so the
flake `nixConfig` is honored during rebuilds.

View File

@@ -0,0 +1,218 @@
# Hyprland Lua Migration Checklist
This checklist tracks the migration described in `docs/tiling-wm-experience.md`.
Guiding rule for shelling out:
- Prefer Lua for compositor/window/workspace state changes.
- Avoid `hyprctl` for window manipulation unless there is no usable Lua API.
- `hyprctl` remains acceptable for non-window-control escape hatches such as
`hyprctl reload`.
- External utilities remain acceptable where they are the real tool being
launched, for example rofi, cliphist, grim/slurp/swappy, playerctl, hyprlock,
and systemd commands.
## 0. Version And Build Base
- [x] Update/confirm Hyprland Lua input at latest usable upstream target.
- [x] Keep stable Hyprland path intact until Lua path is proven.
- [x] Keep hy3 out of the Lua branch.
- [x] Keep hyprNStack following the Lua Hyprland input.
- [x] Rebuild hyprNStack against the Lua Hyprland branch.
- [x] Add a forked hyprexpo input for the Lua Hyprland branch.
- [x] Keep a cheap Lua check: parse config, execute against stub, reject
`hyprctl` in the Lua config's window/workspace manipulation path.
- [x] Add a real Hyprland Lua verifier check for the config parser path.
Current upstream note: latest Hyprland release observed during this migration is
`v0.54.3`; the Lua config input tracks PR 13817 and was already at the current
PR head `c35a8a5` dated 2026-04-26. The non-Lua fallback remains pinned to the older
hy3/hyprexpo-compatible stack; the Lua branch uses forked hyprexpo branch
`colonelpanic8/hyprland-plugins:hyprexpo-lua-hyprland`.
## 1. Core Layout
- [x] Primary layout is equal-width columns.
- [x] No scrolling layout.
- [x] No hy3 in Lua path.
- [x] Dynamic redistribution on open/close via Lua-managed nStack count.
- [x] Monocle/tabbed-style layout available.
- [x] Direct jump to columns layout.
- [x] Direct jump to monocle layout.
- [x] Directional focus cycles in monocle.
- [x] Visual indication of hidden monocle windows, currently notification.
- [x] Make layout state per workspace instead of one global current layout.
- [x] Preserve one-window smart gaps in the live config path.
- [x] Use a persistent monocle indicator instead of a transient notification.
Smart-gaps note: nStack uses `no_gaps_when_only = true`; Hyprland workspace
rules are still applied at runtime for broader parity, but skipped during
`--verify-config` because the current Lua PR segfaults when rule bindings run in
verifier mode.
## 2. Workspace Behavior
- [x] `Super+1..9` focuses bounded workspaces.
- [x] `Super+Shift+1..9` sends window without following.
- [x] `Super+Ctrl+1..9` sends and follows.
- [x] Previous workspace per monitor uses Lua-tracked history.
- [x] Implement next empty workspace focus in Lua.
- [x] Implement move focused window to next empty workspace without following.
- [x] Implement move focused window to next empty workspace and follow.
- [x] Implement bounded workspace cycling `1..9` in Lua, replacing
`workspace-scroll.sh`.
- [x] Implement workspace swap or decide whether native dispatcher is enough.
- [x] Track current monitor workspace history explicitly, with native
`previous_per_monitor` as fallback.
## 3. Directional Navigation
- [x] `Super+w/a/s/d` focuses windows.
- [x] `Super+Shift+w/a/s/d` swaps windows.
- [x] `Hyper+w/a/s/d` focuses monitors.
- [x] `Hyper+Shift+w/a/s/d` moves windows to monitors.
- [x] `Super+z` next monitor.
- [x] `Super+Shift+z` move to next monitor.
- [x] Replace any old cursor-follow/move scripts fully.
- [x] Add required `Super+Ctrl+w/a/s/d` move-to-monitor behavior preserving
useful focus.
- [x] Add "move to empty workspace on monitor in direction" without requiring
`Hyper+Ctrl`.
- [x] Route directional focus in monocle through deterministic Lua cycling.
- [ ] Live-verify directional focus in monocle behaves predictably.
## 4. Script Elimination Priority
- [x] Core layout switching no longer uses scripts.
- [x] Core column count logic no longer uses scripts or `hyprctl`.
- [x] Replace `find-empty-workspace.sh`.
- [x] Replace `workspace-goto-empty.sh`.
- [x] Replace `workspace-move-to-empty.sh`.
- [x] Replace `workspace-scroll.sh`.
- [x] Replace `cycle-layout.sh`.
- [x] Replace `movewindow-follow-cursor.sh`.
- [x] Replace `gather-class.sh`.
- [x] Replace `focus-next-class.sh`.
- [x] Replace `raise-or-run.sh`.
- [x] Replace minimize scripts if Lua can maintain hidden workspace state.
- [x] Replace `swap-workspaces.sh`.
- [x] Decide whether rofi-backed pickers remain scripts or become
Lua-generated command pipes. Rofi itself remains external.
## 5. Overview And Window Discovery
- [x] Restore visual hyprexpo for `Super+Tab` overview.
- [x] Restore visual hyprexpo `bring` mode for `Super+Shift+Tab`.
- [x] Keep first-pass Lua numbered window picker on secondary bindings.
- [x] Implement first-pass Lua-native go-to-window picker.
- [x] Implement first-pass Lua-native bring-window picker.
- [x] Implement first-pass Lua-native replace-window picker.
- [ ] Picker entries include icons.
- [x] Picker entries include title/workspace.
- [x] Hide scratchpad/minimized/internal windows from normal pickers.
- [x] Decide whether picker data generation can be Lua-native with rofi as only
external process.
Picker decision: current Lua API can query and manipulate windows directly, but
does not expose a synchronous way to run rofi and consume its selected output.
The first pass therefore uses Lua-native numbered submaps and notifications.
A final rofi/icon picker would need either a small IPC bridge or an upstream Lua
process-output/callback primitive.
Hyprexpo decision: hyprexpo is kept as the visual overview. The forked Lua
branch exposes `hl.plugin.hyprexpo.expo(...)`, so the Lua config can invoke
`toggle` and `bring` directly without shelling out to `hyprctl`.
## 6. Scratchpads
- [x] Preserve named scratchpads: element, gmail, htop, messages, slack,
spotify, transmission, volume.
- [x] Preserve dropdown terminal scratchpad.
- [x] Scratchpads near-fullscreen and centered.
- [x] Scratchpads hidden from normal listings/status bar.
- [x] Toggling scratchpad exits fullscreen/monocle state first.
- [x] Decide hyprscratch daemon is not needed in the Lua branch.
- [x] Replace `hyprscratch toggle` with Lua-managed scratchpad toggles.
- [x] Disable hyprscratch service on the Lua branch.
- [x] Handle delayed class/title assignment with window class/title event adoption.
- [x] Handle already-running app.
- [x] Handle minimized app.
- [x] Handle app on another workspace.
## 7. Minimization
- [x] Implement minimize active window.
- [x] Implement restore last minimized window.
- [x] Exclude minimized windows from layout.
- [x] Exclude minimized windows from normal go/bring lists.
- [x] Implement minimized picker.
- [x] Implement restore all minimized.
- [x] Implement minimize other windows of current workspace class.
- [x] Implement restore windows of focused class.
- [x] Decide hidden workspace naming/state model for minimized windows.
- [x] Hydrate minimized-window state from the hidden workspace on restore/picker
paths.
## 8. Class-Aware Workflows
- [x] Gather all windows of focused class onto current workspace.
- [x] Focus next window of different/same class as desired parity.
- [x] Browser raise-or-spawn.
- [x] Window info command exposes class/title/workspace/address/pid.
- [ ] Window menus expose real window icons.
- [x] Prefer Lua window queries over `hyprctl clients`.
## 9. Status Bar Contract
- [ ] Confirm taffybar can still list normal workspaces.
- [ ] Confirm special scratchpad/minimize workspaces are filtered.
- [ ] Confirm active workspace per monitor remains visible.
- [ ] Confirm class/title/active/minimized/urgent metadata is available.
- [x] Expose layout name/state if practical.
- [ ] Confirm workspace/window positioning remains enough for icon strips.
Layout state note: Lua writes `$XDG_RUNTIME_DIR/hyprland-layout-state` with the
active workspace, active layout, and per-workspace layout map. Taffybar still
needs a live readback check.
## 10. Session And Utilities
- [x] Terminal binding preserved.
- [x] Launcher/run menu preserved.
- [x] Media keys preserved.
- [x] Clipboard history binding preserved.
- [x] Screenshot binding preserved.
- [x] Lock binding preserved.
- [x] Session startup target integration preserved.
- [x] `hyprctl reload` may remain available as a non-window-manipulation escape
hatch.
- [x] Resolve `Hyper+w` conflict: monitor focus must win; wallpaper picker
needs another key.
- [x] Keep rofi utility commands as external commands unless there is a
meaningful Lua replacement.
- [x] Decide which shell utilities are acceptable because they are not Hyprland
control scripts.
## 11. Validation
- [x] Lua syntax check.
- [x] Lua stub execution check.
- [x] `hyprctl` rejection in Lua config for window/workspace manipulation.
- [x] Real `Hyprland --verify-config` check.
- [x] hyprNStack flake build check.
- [x] hyprexpo Lua-branch flake build check.
- [x] `ryzen-shine` system dry-run.
- [x] `just switch` activates successfully and deploys branch-owned
`~/.config/hypr/hyprland.lua`.
- [x] Re-run checks after Hyprland/Lua input confirmation.
- [ ] Try live compositor smoke test again after version bump.
- [x] Document `--verify-config` caveats for Lua rule/plugin-specific config.
- [x] Eventually run `just switch` only when the branch is coherent enough for a
live test.
Live-smoke note: this Hyprland binary exposes `--verify-config` but no
`--headless` CLI flag. `just switch` now installs the Lua branch binary and
deploys `hyprland.lua`, but the currently running compositor remains the old
0.53 process until the Hyprland session is restarted. A true compositor smoke
test still needs a session restart or a nested Wayland session that avoids
startup side effects.

View File

@@ -0,0 +1,368 @@
# Tiling WM Experience Spec
This document describes the tiling window manager experience I am targeting.
## Priority Levels
- Required: daily-driver behavior.
- Important: expected for parity, but a rough first version is acceptable.
- Nice: useful polish or compatibility.
## Modifier Terminology
- `Super` names the physical modifier key often labeled Windows, Command, GUI,
or OS depending on the keyboard.
- `Hyper` means a higher-order logical modifier layer used for monitor,
workspace, utility, and cross-context operations.
- Prefer implementing `Hyper` as its own virtual modifier or equivalent logical
mask when the environment supports that.
- If a dedicated virtual `Hyper` mask is not practical, `Ctrl+Alt+Super` is the
fallback chord.
- The fallback `Hyper` chord intentionally does not include `Shift`; portable
`Hyper` bindings only use the plain `Hyper` layer and the `Hyper+Shift`
layer.
- Do not require `Hyper+Ctrl`, `Hyper+Alt`, or `Hyper+Super` bindings. Those
modifiers may already be part of the fallback `Hyper` chord.
- Binding descriptions should use `Super` and `Hyper` rather than
hardware-vendor names.
## Workspaces and Monitors
Required behavior:
- Workspaces are a shared global set, not independent per-monitor namespaces.
- Focusing workspace `N` shows workspace `N` on the currently focused monitor.
- Moving a window to workspace `N` does not require caring which monitor
currently owns that workspace.
- Sending the focused window to workspace `N` without following it is a
first-class operation.
- Moving the focused window to workspace `N` and following it is a first-class
operation.
- Sending the focused window to the next empty workspace without following it is
a first-class operation.
- Moving the focused window to the next empty workspace and following it is a
first-class operation.
- Normal workspaces are bounded to `1..9`.
- Workspace history is tracked per monitor.
- Last-workspace toggle uses the current monitor's workspace history.
- Workspace cycling works on the current monitor within the bounded workspace
set.
Important behavior:
- Swapping the current workspace contents with another workspace is available.
- Moving a window to an empty workspace on another monitor is available.
- Moving the focused window to another monitor without following keeps keyboard
focus on the original monitor.
- Moving the focused window to another monitor and following it moves keyboard
focus to the destination monitor.
- Hidden/special workspaces exist for scratchpad state.
- Hidden/special workspaces exist for minimized state.
- Hidden/special workspaces are excluded from ordinary workspace cycling.
- Hidden/special workspaces are excluded from the status bar's normal workspace
list.
## Directional Navigation
Required behavior:
- Directional window focus is available.
- Directional window swapping or movement is available.
- Directional move-to-monitor is available while preserving useful focus.
- Directional monitor focus is available.
- Directional window movement between monitors is available.
- Moving the focused window to an empty workspace on the monitor in a direction
remains required behavior, but it should not require an extra `Hyper`
modifier beyond `Shift`.
- `Super+w/a/s/d` focuses windows directionally.
- `Super+Shift+w/a/s/d` swaps or moves the focused window directionally.
- `Super+Ctrl+w/a/s/d` moves the focused window to the monitor in that
direction while preserving useful focus.
- `Super+Ctrl+Shift+w/a/s/d` moves the focused window to an empty workspace on
the monitor in that direction.
- `Hyper+w/a/s/d` focuses monitors directionally.
- `Hyper+Shift+w/a/s/d` swaps or moves windows between monitors directionally.
- Directional focus in tabbed/fullscreen mode should cycle predictably through
windows even though their screen geometry overlaps.
Important behavior:
- Keyboard resize remains available, but it should not displace the directional
move-to-monitor binding.
## Layouts
Required behavior:
- Tiling is dynamic.
- Primary layout is equal-width vertical columns.
- Scrolling layouts are not acceptable.
- All ordinary splits are vertical.
- Adding windows dynamically redistributes all tiled windows evenly.
- Removing windows dynamically redistributes all tiled windows evenly.
- Ordinary use should not require manually managing a split tree.
- Tabbed/fullscreen-style monocle layout is available.
- Directional window navigation bindings continue to switch windows in
tabbed/fullscreen mode.
- The important layouts are columns and tabbed/fullscreen.
- Dialogs float.
- Dialogs are centered.
- There is a command to jump directly to the columns layout and one to jump
directly to the tabbed/fullscreen layout.
- Layout state is per workspace when the compositor supports it.
Important behavior:
- One-window workspaces should have no visible gaps or use smart gaps.
Nice behavior:
- Gaps can be toggled.
- Fullscreen can be toggled.
- Smart borders can be toggled.
- Layout-related modifiers remain available for experiments.
- Inactive windows are slightly dimmed when supported.
## Overview and Discovery
Required behavior:
- There is an expose-style way to inspect open windows or workspaces before
jumping.
- There is a rofi-style window picker.
- Window picker entries show icons.
- Window picker entries show titles.
- Window picker entries show workspace labels.
- Go-to-window focuses the selected window wherever it currently lives.
- Bring-window moves a selected non-visible window to the current workspace and
focuses it.
- Replace-window swaps the focused window with a selected window where feasible.
Important behavior:
- Overview supports both "go" and "bring" workflows.
- Window switchers hide scratchpad windows unless the user is explicitly using a
scratchpad picker.
- Window switchers hide minimized windows unless the user is explicitly using a
minimized picker.
- Window switchers hide internal windows.
- Go/bring actions unminimize selected windows when needed.
## Scratchpads
Required behavior:
- A named scratchpad exists for element.
- A named scratchpad exists for gmail.
- A named scratchpad exists for htop.
- A named scratchpad exists for messages.
- A named scratchpad exists for slack.
- A named scratchpad exists for spotify.
- A named scratchpad exists for transmission.
- A named scratchpad exists for volume.
- Scratchpads appear near-fullscreen and centered by default.
- Toggling a scratchpad deactivates fullscreen/tabbed state first.
- Scratchpads are hidden from normal workspace and window listings.
Important behavior:
- A dropdown terminal scratchpad exists.
- Scratchpad matching handles delayed class/title assignment.
- Scratchpad behavior is robust when the app is already running.
- Scratchpad behavior is robust when the app is minimized.
- Scratchpad behavior is robust when the app is on another workspace.
## Minimization
Required behavior:
- Focused window can be minimized.
- Last minimized window can be restored to the current workspace and focused.
- Minimized windows are excluded from normal layout.
- Minimized windows are excluded from ordinary go/bring lists.
Important behavior:
- A minimized picker mode exists.
- Restore-all-minimized exists.
- Other classes in the current workspace can be minimized.
- Windows of the focused class can be restored.
- All minimized windows can be restored.
## Class-Aware Workflows
Important behavior:
- Gather all windows of the focused class onto the current workspace.
- Raise-or-spawn exists for the browser.
- Window menus show class.
- Window menus show title.
- Window menus show workspace.
- Window menus show icon.
## Status Bar Contract
Required behavior:
- The status bar can list normal workspaces.
- The status bar can identify the active workspace per monitor.
- The status bar can list windows per workspace.
- The status bar can expose class hints for each listed window.
- The status bar can expose title for each listed window.
- The status bar can expose active state for each listed window.
- The status bar can expose minimized state when available.
- The status bar can expose urgency when available.
- The status bar can expose approximate window position when available.
- Scratchpad workspaces are marked as special or filtered out.
- Minimized workspaces are marked as special or filtered out.
- Internal workspaces are marked as special or filtered out.
Important behavior:
- Workspace labels are stable.
- Workspace icons are stable.
- Window positioning information is available enough for workspace icon strips
and future expose-like views.
- Layout information is available enough for workspace icon strips and future
expose-like views.
- Layout name is exposed if practical.
- Layout state is exposed if practical.
## Session and Utility Behavior
Important behavior:
- Terminal is `ghostty --gtk-single-instance=false`.
- Launcher is `rofi -show drun -show-icons`.
- Run menu is `rofi -show run`.
- Browser raise/spawn behavior exists.
- Border width is effectively zero.
- The status bar can be toggled per monitor.
- Session startup integrates with the normal graphical-session target.
- Session startup integrates with any required session-specific user target.
Nice behavior:
- Wallpaper behavior remains consistent.
- Wallpaper selection uses `Hyper+comma`; `Hyper+w/a/s/d` are reserved for
directional monitor focus.
- Idle behavior remains consistent.
- Lock behavior remains consistent.
- Clipboard history behavior remains consistent.
- Screenshot behavior remains consistent.
- Monitor DDC/input switching remains consistent.
- Rofi utility bindings remain consistent.
- Media keys remain consistent.
## Binding Appendix
Required behavior:
- `Hyper` bindings should remain available from a single physical key where
practical, even if that key emits the fallback chord internally.
- Extra modifiers on `Hyper` are limited to `Shift` for portable bindings.
Important behavior:
- `Hyper` utility bindings must not displace required directional monitor
bindings on `Hyper+w/a/s/d`.
### Core Bindings
Required behavior:
- `Super+p` opens the application launcher.
- `Super+Shift+p` opens the run menu.
- `Super+Shift+Return` opens a terminal.
- `Super+Tab` opens the overview.
- `Super+Shift+Tab` opens the overview in bring-window mode when supported.
- `Super+g` opens the go-to-window picker.
- `Super+b` opens the bring-window picker.
- `Super+Shift+b` opens the replace-window picker.
- `Super+\` toggles to the previous workspace on the current monitor.
- `Super+Shift+e` moves the focused window to the next empty workspace and
follows it. This is the target replacement for the older `Super+Shift+h`
binding.
- `Hyper+e` focuses the next empty workspace.
- `Hyper+5` swaps the current workspace with a selected workspace.
- `Hyper+g` gathers windows of the focused class onto the current workspace.
### Directional Navigation Bindings
Required behavior:
- `Super+w/a/s/d` focuses windows directionally.
- `Super+Shift+w/a/s/d` swaps or moves the focused window directionally.
- `Super+Ctrl+w/a/s/d` moves the focused window to the monitor in that
direction while preserving useful focus.
- `Hyper+w/a/s/d` focuses monitors directionally.
- `Hyper+Shift+w/a/s/d` swaps or moves windows between monitors directionally.
- Moving the focused window to an empty workspace on the monitor in a direction
remains required behavior, but it should not require a `Hyper+Ctrl` binding.
- `Super+z` focuses the next monitor.
- `Super+Shift+z` moves the focused window to the next monitor.
### Numbered Workspace Bindings
Required behavior:
- `Super+1..9` focuses workspace `1..9` on the current monitor.
- `Super+Shift+1..9` sends the focused window to workspace `1..9` without
following it.
- `Super+Ctrl+1..9` sends the focused window to workspace `1..9` and follows
it.
### Scratchpad Bindings
Required behavior:
- `Super+Alt+e` toggles the element scratchpad.
- `Super+Alt+g` toggles the gmail scratchpad.
- `Super+Alt+h` toggles the htop scratchpad.
- `Super+Alt+m` toggles the messages scratchpad.
- `Super+Alt+k` toggles the slack scratchpad.
- `Super+Alt+s` toggles the spotify scratchpad.
- `Super+Alt+t` toggles the transmission scratchpad.
- `Super+Alt+v` toggles the volume scratchpad.
Important behavior:
- `Super+Alt+grave` toggles the dropdown terminal scratchpad.
- `Super+Alt+c` raises or starts the browser.
- `Super+Alt+Return` enters the minimized-window picker or restores minimized
windows, depending on environment support.
- `Super+Alt` is reserved for app-specific raise/spawn, scratchpad, and
scratchpad-adjacent bindings.
### Utility Bindings
Required behavior:
- `Hyper+v` opens clipboard history with a rofi-backed clipboard command
such as `greenclip print` or `cliphist`.
- `Hyper+p` opens the password picker with `rofi-pass`.
- `Hyper+h` opens the screenshot tool with the compositor/session-appropriate
screenshot command.
- `Hyper+c` opens a shell command prompt with `shell_command.sh`.
- `Hyper+x` opens the command picker with `rofi_command.sh`.
- `Hyper+k` opens the process killer with `rofi_kill_process.sh`.
- `Hyper+Shift+k` opens the kill-all/process-tree killer with
`rofi_kill_all.sh`.
- `Hyper+r` opens the systemd/service menu with `rofi-systemd`.
- `Hyper+slash` toggles the status bar with the status-bar-appropriate command.
- `Hyper+backslash` toggles the monitor input with `mpg341cx_input toggle`.
- `Hyper+i` opens the audio input selector with `rofi_select_input.hs`.
- `Hyper+o` opens the audio output selector with `rofi_paswitch`.
- `Hyper+y` opens the agentic skill picker with `rofi_agentic_skill`.
- `Hyper+Shift+l` locks the session with the compositor/session-appropriate
locker.
Important behavior:
- Wallpaper selection is available under `Hyper` via `rofi_wallpaper.sh`, but
its exact key must avoid the required `Hyper+w/a/s/d` directional monitor
bindings.
- Expose-style overview remains available as a utility binding using the
compositor-appropriate implementation.
- Session-destructive operations use shifted or otherwise harder-to-hit
variants.

View File

@@ -1,7 +1,8 @@
# Agentic Session Preferences
## Tmux session titling
- If the TMUX environment variable is set, treat this chat as the controller for the current tmux session.
## Multiplexer session titling
- If the `TMUX` or `ZELLIJ` environment variable is set, treat this chat as the controller for the current tmux or zellij session.
- Use `set_multiplexer_title '<project> - <task>'` to update the title. The command detects tmux vs. zellij internally, prefers tmux when both are present, and no-ops outside a multiplexer.
- Maintain a session/window/pane title that updates when the task focus changes substantially.
- Prefer automatic titling: infer a concise <task> from the current user request and context without asking.
- Title format: "<project> - <task>".
@@ -10,12 +11,114 @@
- <task> is a short, user-friendly description of what we are doing.
- Ask for a short descriptive <task> only when the task is ambiguous or you are not confident in an inferred title.
- When the task changes substantially, update the <task> automatically if clear; otherwise ask for an updated <task>.
- When a title is provided or updated, immediately run this one-liner:
tmux rename-session '<project> - <task>' \; rename-window '<project> - <task>' \; select-pane -T '<project> - <task>'
- Assume you are inside tmux, so do not use -t unless the user asks to target a specific session.
- When a title is provided or updated, immediately run `set_multiplexer_title '<project> - <task>'`; do not call raw tmux or zellij rename commands unless debugging the helper itself.
- For Claude Code sessions, a UserPromptSubmit hook will also update titles automatically based on the latest prompt.
## Pane usage
- Do not create extra panes or windows unless the user asks.
## Git worktrees
- Default to creating git worktrees under a project-local `.worktrees/` directory at the repository root.
- For a repository at `<repo_root>`, use worktree paths like `<repo_root>/.worktrees/<task-or-branch>`.
- Create `.worktrees/` if needed before running `git worktree add`.
- Only use a non-`.worktrees/` location when the user explicitly asks for a different path.
## NixOS workflow
- This system is managed with a Nix flake at `~/dotfiles/nixos`.
- Use `just switch` from that directory for rebuilds instead of plain `nixos-rebuild`.
- Host configs live under `machines/`; choose the appropriate host when needed.
## Ad-hoc utilities via Nix
- If you want to use a CLI utility you know about but it is not currently available on PATH, prefer using `nix run` / `nix shell` to get it temporarily rather than installing it globally.
- Use `nix run` for a single command:
nix run nixpkgs#ripgrep -- rg -n "pattern" .
- Use `nix shell` when you need multiple tools available for a short sequence of commands:
nix shell nixpkgs#{jq,ripgrep} --command bash -lc 'rg -n "pattern" . | head'
- If you are not sure what the package is called in nixpkgs, use:
nix search nixpkgs <name-or-keyword>
## Personal Information
- Full Legal Name: Ivan Anthony Malison
- Email: IvanMalison@gmail.com
- Country of Citizenship: United States of America
- Birthday: August 2, 1990 (1990-08-02)
- Address: 100 Broderick St APT 401, San Francisco, CA 94117, United States
- Employer: Railbird Inc.
- GitHub: colonelpanic8
- Phone: 301-244-8534
- Primary Credit Card: Chase-Reserve
## Repository Overview
This is an org-mode repository containing personal task management, calendars, habits, and project tracking files. It serves as the central hub for Ivan's personal organization.
## Available Tools
### Chrome DevTools MCP
A browser automation MCP is available for interacting with web pages. Use it to:
- Navigate to websites and fill out forms
- Take screenshots and snapshots of pages
- Click elements, type text, and interact with web UIs
- Read page content and extract information
- Automate multi-step web workflows (booking, purchasing, form submission, etc.)
### Google Workspace CLI (`gws`)
The local `gws` CLI is available for Google Workspace operations. Use it to:
- Search, read, and send Gmail messages
- Manage Gmail labels and filters
- Download attachments and inspect message payloads
- Access Drive, Calendar, Docs, Sheets, and other Google Workspace APIs
## Credentials via `pass`
Many credentials and personal details are stored in `pass` (the standard unix password manager). There are hundreds of entries covering a wide range of things, so always search before asking the user for information. Use `pass find <keyword>` to search and `pass show <entry>` to retrieve values.
Examples of what's stored:
- Personal documents - driver's license, passport number, etc.
- Credit/debit cards - card numbers, expiration, CVV for various cards
- Banking - account numbers, online banking logins
- Travel & loyalty - airline accounts, hotel programs, CLEAR, etc.
- Website logins - credentials for hundreds of services
- API keys & tokens - GitHub, various services
- The store is regularly updated with new entries. Always do a dynamic lookup with `pass find` rather than assuming what's there.
- Provide credentials to tools/config at runtime via environment variables or inline `pass` usage instead of committing them.
- Never hardcode credentials or store them in plain text files.
## Guidelines
- When filling out forms or making purchases, pull personal info from this file and credentials from `pass` rather than asking the user to provide them.
- For web tasks, prefer using the Chrome DevTools MCP to automate interactions directly.
- For email tasks, prefer using `gws gmail` over navigating to Gmail in the browser.
- If a task requires a credential not found in `pass`, ask the user rather than guessing.
- This repo's org files (gtd.org, calendar.org, habits.org, projects.org) contain task and scheduling data. The org-agenda-api skill/service can also be used to query agenda data programmatically.
## Project links (local symlink index)
- Paths in this section are relative to this file's directory (`dotfiles/agents/`).
- Keep a local symlink index under `./project-links/` for projects that are frequently referenced.
- Treat these links as machine-local discovery state maintained by agents (do not commit machine-specific targets).
- Reuse existing symlinks first. If a link is missing or stale, search for the repo, then update the link with:
ln -sfn "<absolute-path-to-repo>" "./project-links/<link-name>"
- If a project cannot be found quickly, do a targeted search (starting from likely roots) and only then widen the search.
## Project constellation guides
- Keep per-constellation context in `./project-guides/` and keep this file minimal.
- When a request involves one of these projects:
- Open the guide first.
- If a mentioned repo/package name matches a guide's related-project list, open that guide even if the user did not name the constellation explicitly.
- Ensure required links exist under `./project-links/`.
- If links are missing, run a targeted search from likely roots, then create/update the symlink.
- Guide index:
- `./project-guides/mova-org-agenda-api.md`
- `./project-guides/taffybar.md`
- `./project-guides/railbird.md`
- `./project-guides/org-emacs-packages.md`

View File

@@ -1,25 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ -z "${TMUX:-}" ]]; then
exit 0
fi
input=$(cat)
read -r cwd prompt <<'PY' < <(python3 - <<'PY'
mapfile -d '' -t parsed < <(PAYLOAD="$input" python3 - <<'PY'
import json, os, sys
try:
data = json.load(sys.stdin)
data = json.loads(os.environ.get("PAYLOAD", ""))
except Exception:
data = {}
cwd = data.get("cwd") or os.getcwd()
prompt = (data.get("prompt") or "").strip()
print(cwd)
print(prompt)
sys.stdout.write(cwd)
sys.stdout.write("\0")
sys.stdout.write(prompt)
sys.stdout.write("\0")
PY
)
cwd="${parsed[0]:-}"
prompt="${parsed[1]:-}"
if [[ -z "${cwd}" ]]; then
cwd="$PWD"
@@ -46,25 +46,16 @@ if [[ -z "$task" ]]; then
task="work"
fi
# Trim to a reasonable length for tmux status bars.
# Trim to a reasonable length for multiplexer UI labels.
if [[ ${#task} -gt 60 ]]; then
task="${task:0:57}..."
fi
title="$project - $task"
state_dir="${HOME}/.agents/state"
state_file="$state_dir/tmux-title"
mkdir -p "$state_dir"
if [[ -f "$state_file" ]]; then
last_title=$(cat "$state_file" 2>/dev/null || true)
if [[ "$last_title" == "$title" ]]; then
exit 0
fi
if command -v set_multiplexer_title >/dev/null 2>&1; then
set_multiplexer_title "$title"
else
hook_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
"$hook_dir/../../lib/functions/set_multiplexer_title" "$title"
fi
printf '%s' "$title" > "$state_file"
# Update session, window, and pane titles.
tmux rename-session "$title" \; rename-window "$title" \; select-pane -T "$title"

View File

@@ -0,0 +1,29 @@
# Mova / org-agenda-api constellation
## Scope
- Use this guide for requests involving the mova constellation, including `org-agenda-api`.
- Primary anchor is the mova root repo; start there and branch out.
## Related packages/projects (trigger list)
- If any of these names are mentioned, open this guide for context.
- `mova-dev`: coordination repo for the mova ecosystem and cross-repo workflows.
- `mova`: React Native app (iOS/Android/Web).
- `org-agenda-api`: Emacs Lisp HTTP API and deployment container.
- `org-window-habit`: habit-tracking logic used by org workflows.
- `org-wild-notifier`: org notification logic and scheduling behavior.
- `dotfiles` (within mova-dev context): infra/config and deployment glue for org-agenda-api.
## Symlink targets
- `./project-links/mova-dev` -> mova constellation root.
## Discovery hints
- Check likely roots first, especially `~/Projects`.
- Common local path is `~/Projects/mova-dev`, but do not assume it exists.
- If the symlink is missing or stale, search by directory name first, then by repo names.
## Read-first docs
- `./project-links/mova-dev/README.md`
- `./project-links/mova-dev/org-agenda-api/README.md` (if present)
## Notes
- Prefer treating mova root docs as canonical project context.

View File

@@ -0,0 +1,25 @@
# Org / Emacs package constellation
## Scope
- Use this guide for org-related package repos, including `org-window-habit`.
- This is especially relevant when repos are managed through local Emacs package trees.
## Related packages/projects (trigger list)
- If any of these names are mentioned, open this guide for context.
- `org-window-habit`: org habit-tracking package/repo.
- `org-wild-notifier`: org notification package/repo.
- `org-agenda-api`: Emacs Lisp HTTP API project that loads org package deps.
- `elpaca`: Emacs package manager tree where local checkouts may live.
- `elpa`: traditional Emacs package install tree (fallback search area).
## Symlink targets
- `./project-links/org-window-habit` -> org-window-habit repo/root.
## Discovery hints
- Start with Emacs roots, especially `~/.emacs.d`.
- Prefer checking package manager trees (including `elpaca`) before broader searches.
- Common pattern is nested repos under `~/.emacs.d` package directories.
## Read-first docs
- `./project-links/org-window-habit/README.md`
- `./project-links/org-window-habit/README.org` (if present)

View File

@@ -0,0 +1,28 @@
# Railbird constellation
## Scope
- Use this guide for requests involving railbird backend/main repo and railbird mobile app work.
## Related packages/projects (trigger list)
- If any of these names are mentioned, open this guide for context.
- `railbird`: primary backend/main railbird repository.
- `railbird-mobile`: primary mobile app repository.
- `railbird2`: alternate/new-generation backend repo.
- `railbird-mobile2`: alternate/new-generation mobile repo.
- `railbird-docs`: documentation repository.
- `railbird-landing-page`: marketing/landing site repository.
- `railbird-alert-tuning`: alert/tuning and operational experimentation repo.
- `railbird-agents-architecture`: architecture notes/prototypes for agent workflows.
## Symlink targets
- `./project-links/railbird` -> primary railbird repo.
- `./project-links/railbird-mobile` -> railbird mobile app repo.
## Discovery hints
- Start from `~/Projects`.
- Common backend location is `~/Projects/railbird`.
- Mobile repo often also lives under `~/Projects`, but name/path may vary by machine.
## Read-first docs
- `./project-links/railbird/README.md`
- `./project-links/railbird-mobile/README.md` (if present)

View File

@@ -0,0 +1,30 @@
# Taffybar constellation
## Scope
- Use this guide for requests involving taffybar itself or local taffybar configuration.
## Related packages/projects (trigger list)
- If any of these names are mentioned, open this guide for context.
- `taffybar`: top-level desktop bar library/app.
- `imalison-taffybar`: personal taffybar configuration package/repo.
- `gtk-sni-tray`: StatusNotifier tray integration for taffybar.
- `gtk-strut`: X11/WM strut handling used by taffybar ecosystem.
- `status-notifier-item`: StatusNotifier protocol/types library.
- `dbus-menu`: DBus menu protocol support used by tray integrations.
- `dbus-hslogger`: DBus logging helper used in ecosystem packages.
## Symlink targets
- `./project-links/taffybar-main` -> main taffybar repo.
- `./project-links/taffybar-config` -> local taffybar config root.
## Discovery hints
- Start with `~/.config/taffybar`.
- Common layout is:
- config root at `~/.config/taffybar`
- main repo at `~/.config/taffybar/taffybar`
- Other taffybar-related repos may exist elsewhere; find them from docs in the main repo.
## Read-first docs
- `./project-links/taffybar-main/README.md`
- `./project-links/taffybar-config/README.md` (if present)
- `./project-links/taffybar-config/AGENTS.md` (if present)

View File

@@ -0,0 +1,2 @@
*
!.gitignore

2
dotfiles/agents/skills/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.system/
codex-primary-runtime/

View File

@@ -0,0 +1,254 @@
---
name: disk-space-cleanup
description: Investigate and safely reclaim disk space on this machine, especially on NixOS systems with heavy Nix, Rust/Haskell, Docker, and Podman usage. Use when disk is low, builds fail with no-space errors, /nix/store appears unexpectedly large, or the user asks for easy cleanup wins without deleting important data.
---
# Disk Space Cleanup
Reclaim disk space with a safety-first workflow: investigate first, run obvious low-risk cleanup wins, then do targeted analysis for larger opportunities.
Bundled helpers:
- `scripts/rust_target_dirs.py`: inventory and guarded deletion for explicit Rust `target/` directories
- `references/rust-target-roots.txt`: machine-specific roots for Rust artifact scans
- `references/ignore-paths.md`: machine-specific excludes for `du`/`ncdu`
## Execution Default
- Start with non-destructive investigation and quick sizing.
- Prioritize easy wins first (`nix-collect-garbage`, container prune, Cargo artifacts).
- Propose destructive actions with expected impact before running them.
- Run destructive actions only after confirmation, unless the user explicitly requests immediate execution of obvious wins.
- Capture new reusable findings by updating this skill before finishing.
## Workflow
1. Establish current pressure and biggest filesystems
2. Run easy cleanup wins
3. Inventory Rust build artifacts and clean the right kind of target
4. Investigate remaining heavy directories with `ncdu`/`du`
5. Investigate `/nix/store` roots when large toolchains still persist
6. Summarize reclaimed space and next candidate actions
7. Record new machine-specific ignore paths, Rust roots, or cleanup patterns in this skill
## Step 1: Baseline
Run a quick baseline before deleting anything:
```bash
df -h /
df -h /home
df -h /nix
```
Optionally add a quick home-level size snapshot:
```bash
du -xh --max-depth=1 "$HOME" 2>/dev/null | sort -h
```
## Step 2: Easy Wins
Use these first when the user wants fast, low-effort reclaiming:
```bash
sudo -n nix-collect-garbage -d
sudo -n docker system prune -a
sudo -n podman system prune -a
```
Notes:
- Add `--volumes` only when the user approves deleting unused volumes.
- Re-check free space after each command to show impact.
- Prefer `sudo -n` first so cleanup runs fail fast instead of hanging on password prompts.
- If root is still tight after these, run app cache cleaners before proposing raw `rm -rf`:
```bash
uv cache clean
pip cache purge
yarn cache clean
npm cache clean --force
```
## Step 3: Rust Build Artifact Cleanup
Do not start with a blind `find ~ -name target` or with hard-coded roots that may miss worktrees. Inventory explicit `target/` directories first using the bundled helper and the machine-specific root list in `references/rust-target-roots.txt`.
Inventory the biggest candidates:
```bash
python /home/imalison/dotfiles/dotfiles/agents/skills/disk-space-cleanup/scripts/rust_target_dirs.py list --min-size 500M --limit 30
```
Focus on stale targets only:
```bash
python /home/imalison/dotfiles/dotfiles/agents/skills/disk-space-cleanup/scripts/rust_target_dirs.py list --min-size 1G --older-than 14 --output tsv
```
Use `cargo-sweep` when the repo is still active and you want age/toolchain-aware cleanup inside a workspace:
```bash
nix run nixpkgs#cargo-sweep -- sweep -d -r -t 30 <workspace-root>
nix run nixpkgs#cargo-sweep -- sweep -r -t 30 <workspace-root>
nix run nixpkgs#cargo-sweep -- sweep -d -r -i <workspace-root>
nix run nixpkgs#cargo-sweep -- sweep -r -i <workspace-root>
```
Use direct `target/` deletion when inventory shows a discrete stale directory, especially for inactive repos or project-local worktrees. The helper only deletes explicit paths named `target` that are beneath configured roots and a Cargo project:
```bash
python /home/imalison/dotfiles/dotfiles/agents/skills/disk-space-cleanup/scripts/rust_target_dirs.py delete /abs/path/to/target
python /home/imalison/dotfiles/dotfiles/agents/skills/disk-space-cleanup/scripts/rust_target_dirs.py delete /abs/path/to/target --yes
```
Recommended sequence:
1. Run `rust_target_dirs.py list` to see the largest `target/` directories across `~/Projects`, `~/org`, `~/dotfiles`, and other configured roots.
2. For active repos, prefer `cargo-sweep` from the workspace root.
3. For inactive repos, abandoned branches, and `.worktrees/*/target`, prefer guarded direct deletion of the explicit `target/` directory.
4. Re-run the list command after each deletion round to show reclaimed space.
Machine-specific note:
- Project-local `.worktrees/*/target` directories are common cleanup wins on this machine and are easy to miss with the old hard-coded workflow.
## Step 4: Investigation with `ncdu` and `du`
Avoid mounted or remote filesystems when profiling space. Load ignore patterns from `references/ignore-paths.md`.
Use one-filesystem scans to avoid crossing mounts:
```bash
ncdu -x "$HOME"
sudo ncdu -x /
```
When excluding known noisy mountpoints:
```bash
ncdu -x --exclude "$HOME/keybase" "$HOME"
sudo ncdu -x --exclude /keybase --exclude /var/lib/railbird /
```
If `ncdu` is missing, use:
```bash
nix run nixpkgs#ncdu -- -x "$HOME"
```
For reusable, mount-safe snapshots on this machine, prefer the local wrapper:
```bash
safe_ncdu /
sudo -n env HOME=/home/imalison safe_ncdu /
safe_ncdu /nix/store
safe_ncdu top ~/.cache/ncdu/latest-root.json.zst 30 /home/imalison
safe_ncdu open ~/.cache/ncdu/latest-root.json.zst
```
`safe_ncdu` writes compressed ncdu exports under `~/.cache/ncdu`, records the exclude list beside the export, excludes mounted descendants of the scan root, and supports follow-up `top` queries without rescanning.
For quick, non-blocking triage on very large trees, prefer bounded probes:
```bash
timeout 30s du -xh --max-depth=1 "$HOME/.cache" 2>/dev/null | sort -h
timeout 30s du -xh --max-depth=1 "$HOME/.local/share" 2>/dev/null | sort -h
```
Machine-specific heavy hitters seen in practice:
- `~/.cache/uv` can exceed 20G and is reclaimable with `uv cache clean`.
- `~/.cache/pypoetry` can exceed 7G across artifacts, repository cache, and virtualenvs; inspect first, then use Poetry cache commands or targeted virtualenv removal.
- `~/.cache/google-chrome` can exceed 8G across multiple Chrome profiles; close Chrome before clearing profile cache directories.
- `~/.cache/spotify` can exceed 10G; treat as optional app-cache cleanup.
- `~/.gradle` can exceed 8G, mostly under `caches/`; prefer Gradle-aware cleanup and expect dependency redownloads.
- `~/.local/share/picom/debug.log` can grow past 15G when verbose picom debugging is enabled or crashes leave a stale log behind; if `picom` is not running, deleting or truncating the log is a high-yield low-risk win.
- `~/.local/share/Trash` can exceed several GB; empty only with user approval.
- `/var/lib/private/gitea-runner` can exceed 50G and is not visible to an unprivileged `ncdu /` scan; use `sudo -n env HOME=/home/imalison safe_ncdu /` when `/var` looks undercounted.
- Validated cleanup pattern: stop `gitea-runner-nix.service`, remove cache/work directories under `/var/lib/private/gitea-runner` (`.cache`, `.gradle`, `action-cache-dir`, `workspace`, stale nested `gitea-runner`, and nested `nix/.cache`/`nix/.local`), recreate `action-cache-dir`, `workspace`, and `.cache` owned by `gitea-runner:gitea-runner`, then restart the service.
- Preserve registration/config-like files such as `/var/lib/private/gitea-runner/nix/.runner`, `/var/lib/private/gitea-runner/nix/.labels`, `/var/lib/private/gitea-runner/.docker/config.json`, and SSH/Kube material.
- `~/Projects/*/target` directories can dominate home usage. Recent example candidates included stale `target/` directories under `scrobble-scrubber`, `http-client-vcr`, `http-client`, `subtr-actor`, `http-types`, `subtr-actor-py`, `sdk`, and `async-h1`.
## Step 5: `/nix/store` Deep Dive
When `/nix/store` is still large after GC, inspect root causes instead of deleting random paths.
Useful commands:
```bash
nix path-info -Sh /nix/store/* 2>/dev/null | sort -h | tail -n 50
nix-store --gc --print-roots
```
Avoid `du -sh /nix/store` as a first diagnostic; it can be very slow on large stores.
For repeated GHC/Rust toolchain copies:
```bash
nix path-info -Sh /nix/store/* 2>/dev/null | rg '(ghc|rustc|rust-std|cargo)'
nix-store --gc --print-roots | rg '(ghc|rust)'
```
Resolve why a path is retained:
```bash
/home/imalison/dotfiles/dotfiles/lib/functions/find_store_path_gc_roots /nix/store/<store-path>
nix why-depends <consumer-store-path> <dependency-store-path>
```
Common retention pattern on this machine:
- Many `.direnv/flake-profile-*` symlinks under `~/Projects` and worktrees keep `nix-shell-env`/`ghc-shell-*` roots alive.
- Old taffybar constellation repos under `~/Projects` can pin large Haskell closures through `.direnv` and `result` symlinks. Deleting `gtk-sni-tray`, `status-notifier-item`, `dbus-menu`, `dbus-hslogger`, and `gtk-strut` and then rerunning `nix-collect-garbage -d` reclaimed about 11G of store data in one validated run.
- `find_store_path_gc_roots` is especially useful for proving GHC retention: many large `ghc-9.10.3-with-packages` paths are unique per project, while the base `ghc-9.10.3` and docs paths are shared.
- NixOS system generations and a repo-root `nixos/result` symlink can pin multiple Android Studio and Android SDK versions. Check `/nix/var/nix/profiles/system-*-link`, `/run/current-system`, `/run/booted-system`, and `~/dotfiles/nixos/result` before assuming Android paths are pinned by project shells.
- `~/Projects/railbird-mobile/.direnv/flake-profile-*` can pin large Android SDK system images. Removing stale direnv profiles there is a more targeted first step than deleting Android store paths directly.
- For a repeatable `/nix/store` `ncdu` snapshot without driving the TUI, export and inspect it:
```bash
ncdu -0 -x -c -o /tmp/nix-store.ncdu.json.zst /nix/store
zstdcat /tmp/nix-store.ncdu.json.zst | jq 'def sumd: if type=="array" then ((.[0].dsize // 0) + ([.[1:][] | sumd] | add // 0)) elif type=="object" then (.dsize // 0) else 0 end; .[3] | sumd'
```
- `nix-store --gc --print-dead` plus the Nix SQLite database is a fast way to estimate immediate GC wins before deleting anything:
```bash
nix-store --gc --print-dead > /tmp/nix-dead-paths.txt
printf '%s\n' '.mode list' '.separator |' 'create temp table dead(path text);' \
'.import /tmp/nix-dead-paths.txt dead' \
'select count(*), sum(narSize) from ValidPaths join dead using(path);' \
| nix shell nixpkgs#sqlite --command sqlite3 /nix/var/nix/db/db.sqlite
```
- Quantify before acting:
```bash
find ~/Projects -type l -path '*/.direnv/flake-profile-*' | wc -l
find ~/Projects -type d -name .direnv | wc -l
nix-store --gc --print-roots | rg '/\\.direnv/flake-profile-' | awk -F' -> ' '{print $1 \"|\" $2}' \
| while IFS='|' read -r root target; do \
nix-store -qR \"$target\" | rg '^/nix/store/.+-ghc-[0-9]'; \
done | sort | uniq -c | sort -nr | head
```
- If counts are high and the projects are inactive, propose targeted `.direnv` cleanup for user confirmation.
## Safety Rules
- Do not delete user files directly unless explicitly requested.
- Prefer cleanup tools that understand ownership/metadata (`nix`, `docker`, `podman`, `cargo-sweep`) over `rm -rf`.
- For Rust build artifacts, deleting an explicit directory literally named `target` is acceptable when it is discovered by the bundled helper; Cargo will rebuild it.
- Present a concise “proposed actions” list before high-impact deletes.
- If uncertain whether data is needed, stop at investigation and ask.
## Learning Loop (Required)
Treat this skill as a living playbook.
After each disk cleanup task:
1. Add newly discovered mountpoints or directories to ignore in `references/ignore-paths.md`.
2. Add newly discovered Rust repo roots in `references/rust-target-roots.txt`.
3. Add validated command patterns or caveats discovered during the run to this `SKILL.md`.
4. Keep instructions practical and machine-specific; remove stale guidance.

View File

@@ -0,0 +1,3 @@
interface:
display_name: "Disk Space Cleanup"
short_description: "Find safe disk-space wins on NixOS hosts"

View File

@@ -0,0 +1,31 @@
# Ignore Paths for Disk Investigation
Use this file to track mountpoints or directories that should be excluded from `ncdu`/`du` scans because they are remote, special-purpose, or noisy.
## Known Ignores
- `$HOME/keybase`
- `$HOME/.cache/keybase`
- `$HOME/.local/share/keybase`
- `$HOME/.config/keybase`
- `/keybase`
- `/var/lib/railbird`
- `/run/user/*/doc` (FUSE portal mount; machine-specific example observed: `/run/user/1004/doc`)
## Discovery Commands
List mounted filesystems and spot special mounts:
```bash
findmnt -rn -o TARGET,FSTYPE,SOURCE
```
Target likely remote/special mounts:
```bash
findmnt -rn -o TARGET,FSTYPE,SOURCE | rg '(keybase|fuse|rclone|s3|railbird)'
```
## Maintenance Rule
When a disk cleanup run encounters a mount or path that should be ignored in future runs, add it here immediately with a short note.

View File

@@ -0,0 +1,6 @@
# One absolute path per line. Comments are allowed.
# Keep this list machine-specific and update it when Rust repos move.
/home/imalison/Projects
/home/imalison/org
/home/imalison/dotfiles

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python3
import argparse
import json
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
DEFAULT_ROOTS_FILE = SCRIPT_DIR.parent / "references" / "rust-target-roots.txt"
def parse_size(value: str) -> int:
text = value.strip().upper()
units = {
"B": 1,
"K": 1024,
"KB": 1024,
"M": 1024**2,
"MB": 1024**2,
"G": 1024**3,
"GB": 1024**3,
"T": 1024**4,
"TB": 1024**4,
}
for suffix, multiplier in units.items():
if text.endswith(suffix):
number = text[: -len(suffix)].strip()
return int(float(number) * multiplier)
return int(float(text))
def human_size(num_bytes: int) -> str:
value = float(num_bytes)
for unit in ["B", "K", "M", "G", "T"]:
if value < 1024 or unit == "T":
if unit == "B":
return f"{int(value)}B"
return f"{value:.1f}{unit}"
value /= 1024
return f"{num_bytes}B"
def is_relative_to(path: Path, root: Path) -> bool:
try:
path.relative_to(root)
return True
except ValueError:
return False
def load_roots(roots_file: Path, cli_roots: list[str]) -> list[Path]:
roots: list[Path] = []
for raw in cli_roots:
candidate = Path(raw).expanduser().resolve()
if candidate.exists():
roots.append(candidate)
if roots_file.exists():
for line in roots_file.read_text().splitlines():
stripped = line.split("#", 1)[0].strip()
if not stripped:
continue
candidate = Path(stripped).expanduser().resolve()
if candidate.exists():
roots.append(candidate)
unique_roots: list[Path] = []
seen: set[Path] = set()
for root in roots:
if root not in seen:
unique_roots.append(root)
seen.add(root)
return unique_roots
def du_size_bytes(path: Path) -> int:
result = subprocess.run(
["du", "-sb", str(path)],
check=True,
capture_output=True,
text=True,
)
return int(result.stdout.split()[0])
def nearest_cargo_root(path: Path, stop_roots: list[Path]) -> str:
current = path.parent
stop_root_set = set(stop_roots)
while current != current.parent:
if (current / "Cargo.toml").exists():
return str(current)
if current in stop_root_set:
break
current = current.parent
return ""
def discover_targets(roots: list[Path]) -> list[dict]:
results: dict[Path, dict] = {}
now = time.time()
for root in roots:
for current, dirnames, _filenames in os.walk(root, topdown=True):
if "target" in dirnames:
target_dir = (Path(current) / "target").resolve()
dirnames.remove("target")
if target_dir in results or not target_dir.is_dir():
continue
stat_result = target_dir.stat()
size_bytes = du_size_bytes(target_dir)
age_days = int((now - stat_result.st_mtime) // 86400)
results[target_dir] = {
"path": str(target_dir),
"size_bytes": size_bytes,
"size_human": human_size(size_bytes),
"age_days": age_days,
"workspace": nearest_cargo_root(target_dir, roots),
}
return sorted(results.values(), key=lambda item: item["size_bytes"], reverse=True)
def print_table(rows: list[dict]) -> None:
if not rows:
print("No matching Rust target directories found.")
return
size_width = max(len(row["size_human"]) for row in rows)
age_width = max(len(str(row["age_days"])) for row in rows)
print(
f"{'SIZE'.ljust(size_width)} {'AGE'.rjust(age_width)} PATH"
)
for row in rows:
print(
f"{row['size_human'].ljust(size_width)} "
f"{str(row['age_days']).rjust(age_width)}d "
f"{row['path']}"
)
def filter_rows(rows: list[dict], min_size: int, older_than: int | None, limit: int | None) -> list[dict]:
filtered = [row for row in rows if row["size_bytes"] >= min_size]
if older_than is not None:
filtered = [row for row in filtered if row["age_days"] >= older_than]
if limit is not None:
filtered = filtered[:limit]
return filtered
def cmd_list(args: argparse.Namespace) -> int:
roots = load_roots(Path(args.roots_file).expanduser(), args.root)
if not roots:
print("No scan roots available.", file=sys.stderr)
return 1
rows = discover_targets(roots)
rows = filter_rows(rows, parse_size(args.min_size), args.older_than, args.limit)
if args.output == "json":
print(json.dumps(rows, indent=2))
elif args.output == "tsv":
for row in rows:
print(
"\t".join(
[
str(row["size_bytes"]),
str(row["age_days"]),
row["path"],
row["workspace"],
]
)
)
elif args.output == "paths":
for row in rows:
print(row["path"])
else:
print_table(rows)
return 0
def validate_delete_path(path_text: str, roots: list[Path]) -> Path:
target = Path(path_text).expanduser().resolve(strict=True)
if target.name != "target":
raise ValueError(f"{target} is not a target directory")
if target.is_symlink():
raise ValueError(f"{target} is a symlink")
if not target.is_dir():
raise ValueError(f"{target} is not a directory")
if not any(is_relative_to(target, root) for root in roots):
raise ValueError(f"{target} is outside configured scan roots")
if nearest_cargo_root(target, roots) == "":
raise ValueError(f"{target} is not beneath a Cargo project")
return target
def cmd_delete(args: argparse.Namespace) -> int:
roots = load_roots(Path(args.roots_file).expanduser(), args.root)
if not roots:
print("No scan roots available.", file=sys.stderr)
return 1
targets: list[Path] = []
for raw_path in args.path:
try:
targets.append(validate_delete_path(raw_path, roots))
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
total_size = sum(du_size_bytes(target) for target in targets)
print(f"Matched {len(targets)} target directories totaling {human_size(total_size)}:")
for target in targets:
print(str(target))
if not args.yes:
print("Dry run only. Re-run with --yes to delete these target directories.")
return 0
for target in targets:
shutil.rmtree(target)
print(f"Deleted {len(targets)} target directories.")
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Inventory and delete Rust target directories under configured roots."
)
parser.add_argument(
"--roots-file",
default=str(DEFAULT_ROOTS_FILE),
help="Path to the newline-delimited root list.",
)
parser.add_argument(
"--root",
action="append",
default=[],
help="Additional root to scan. May be provided multiple times.",
)
subparsers = parser.add_subparsers(dest="command", required=True)
list_parser = subparsers.add_parser("list", help="List target directories.")
list_parser.add_argument("--min-size", default="0", help="Minimum size threshold, for example 500M or 2G.")
list_parser.add_argument("--older-than", type=int, help="Only include targets at least this many days old.")
list_parser.add_argument("--limit", type=int, help="Maximum number of rows to print.")
list_parser.add_argument(
"--output",
choices=["table", "tsv", "json", "paths"],
default="table",
help="Output format.",
)
list_parser.set_defaults(func=cmd_list)
delete_parser = subparsers.add_parser("delete", help="Delete explicit target directories.")
delete_parser.add_argument("path", nargs="+", help="One or more target directories to delete.")
delete_parser.add_argument("--yes", action="store_true", help="Actually delete the paths.")
delete_parser.set_defaults(func=cmd_delete)
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,111 @@
---
name: email-unsubscribe-check
description: Use when user wants to find promotional or unwanted recurring emails to unsubscribe from, or when doing periodic inbox hygiene to identify senders worth unsubscribing from
---
# Email Unsubscribe Check
Scan recent inbox emails to surface promotional, newsletter, and digest senders the user likely wants to unsubscribe from. Actually unsubscribe via browser automation.
## Workflow
```dot
digraph unsubscribe_check {
"Search recent inbox emails" -> "Group by sender domain";
"Group by sender domain" -> "Classify each sender";
"Classify each sender" -> "Obvious unsubscribe?";
"Obvious unsubscribe?" -> "Present to user for confirmation" [label="yes"];
"Obvious unsubscribe?" -> "Borderline?" [label="no"];
"Borderline?" -> "Ask user" [label="yes"];
"Borderline?" -> "Skip" [label="no, personal"];
"Present to user for confirmation" -> "User confirms?";
"User confirms?" -> "Actually unsubscribe" [label="yes"];
"User confirms?" -> "Skip" [label="no"];
"Actually unsubscribe" -> "Mark matching emails read + archive";
"Mark matching emails read + archive" -> "Create Gmail filter";
"Create Gmail filter" -> "Retroactively clean old emails";
}
```
## Execution Default
- Start the workflow immediately when this skill is invoked.
- Do not ask a kickoff question like "should I start now?".
- Default scan window is `newer_than:7d` unless the user already specified a different range.
- Only ask a follow-up question before starting if required information is missing and execution would otherwise be blocked.
- Default user preference: they generally do not want subscription-style email in their inbox.
- For obvious marketing/newsletter/digest mail with a working unsubscribe path, unsubscribe by default without asking for confirmation first.
- Still ask first for borderline cases such as creator subscriptions, professional communities, event platforms, or anything that appears transactional/security-sensitive.
## How to Scan
1. Search recent emails: `newer_than:7d` (or wider if user requests)
2. Identify senders that look promotional/automated/digest
3. Present findings grouped by confidence:
- **Clearly unsubscribeable**: marketing, promos, digests user never engages with
- **Ask user**: newsletters, community content, event platforms (might be wanted)
When the user's standing preference is to keep subscriptions out of the inbox, treat the **Clearly unsubscribeable** bucket as auto-actionable.
## Unsubscribe Execution
For each confirmed sender, do ALL of these:
### 1. Actually unsubscribe via browser (most important step)
Two approaches depending on the sender:
**For emails with unsubscribe links:**
- Read the email via `gws gmail` to find the unsubscribe URL (usually at bottom of email body)
- Navigate to the URL with Chrome DevTools MCP
- Take a snapshot, find the confirmation button/checkbox
- Click through to complete the unsubscribe
- Verify the confirmation page
**For services with email settings pages (Nextdoor, LinkedIn, etc.):**
- Navigate to the service's notification/email settings page
- Log in using credentials from `pass` if needed
- Find and disable all email notification toggles
- Check ALL categories (digests, alerts, promotions, etc.)
### 2. Create Gmail filter as backup
Even after unsubscribing, create a filter to catch stragglers:
```
gws gmail users settings filters create \
--params '{"userId":"me"}' \
--json '{"criteria":{"from":"domain.com"},"action":{"removeLabelIds":["INBOX"]}}'
```
### 3. Mark old emails as read and archive them (minimum hygiene)
After unsubscribing, clean up existing email from the sender.
- At minimum: mark them as read.
- Preferred/default: also archive them (remove `INBOX` label).
Example:
```
gws gmail users messages list --params '{"userId":"me","q":"from:domain.com","maxResults":50}'
gws gmail users messages batchModify \
--params '{"userId":"me"}' \
--json '{"ids":["..."],"removeLabelIds":["UNREAD","INBOX"]}'
```
## Signals That an Email is Unsubscribeable
- "no-reply@" or "newsletter@" sender addresses
- Marketing subject lines: sales, promotions, "don't miss", digests
- Bulk senders: Nextdoor, Yelp, LinkedIn digest, social media notifications
- Community digests the user doesn't engage with
- Financial marketing (not transactional alerts)
- "Your weekly/daily/monthly" summaries
- Messages with explicit unsubscribe/manage-preferences links whose primary purpose is promotional or newsletter delivery
## Signals to NOT Auto-Unsubscribe (Ask First)
- Patreon/creator content
- Event platforms (Luma, Eventbrite, Meetup)
- Professional communities
- Services the user actively uses (even if noisy)
- Transactional emails from wanted services

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,25 @@
---
name: gh-address-comments
description: Help address review/issue comments on the open GitHub PR for the current branch using gh CLI; verify gh auth first and prompt the user to authenticate if not logged in.
metadata:
short-description: Address comments in a GitHub PR review
---
# PR Comment Handler
Guide to find the open PR for the current branch and address its comments with gh CLI. Run all `gh` commands with elevated network access.
Prereq: ensure `gh` is authenticated (for example, run `gh auth login` once), then run `gh auth status` with escalated permissions (include workflow/repo scopes) so `gh` commands succeed. If sandboxing blocks `gh auth status`, rerun it with `sandbox_permissions=require_escalated`.
## 1) Inspect comments needing attention
- Run scripts/fetch_comments.py which will print out all the comments and review threads on the PR
## 2) Ask the user for clarification
- Number all the review threads and comments and provide a short summary of what would be required to apply a fix for it
- Ask the user which numbered comments should be addressed
## 3) If user chooses comments
- Apply fixes for the selected comments
Notes:
- If gh hits auth/rate issues mid-run, prompt the user to re-authenticate with `gh auth login`, then retry.

View File

@@ -0,0 +1,6 @@
interface:
display_name: "GitHub Address Comments"
short_description: Address comments in a GitHub PR review"
icon_small: "./assets/github-small.svg"
icon_large: "./assets/github.png"
default_prompt: "Address all actionable GitHub PR review comments in this branch and summarize the updates."

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill="currentColor" d="M8 1.3a6.665 6.665 0 0 1 5.413 10.56 6.677 6.677 0 0 1-3.288 2.432c-.333.067-.458-.142-.458-.316 0-.226.008-.942.008-1.834 0-.625-.208-1.025-.45-1.233 1.483-.167 3.042-.734 3.042-3.292a2.58 2.58 0 0 0-.684-1.792c.067-.166.3-.85-.066-1.766 0 0-.559-.184-1.834.683a6.186 6.186 0 0 0-1.666-.225c-.567 0-1.134.075-1.667.225-1.275-.858-1.833-.683-1.833-.683-.367.916-.134 1.6-.067 1.766a2.594 2.594 0 0 0-.683 1.792c0 2.55 1.55 3.125 3.033 3.292-.192.166-.367.458-.425.891-.383.175-1.342.459-1.942-.55-.125-.2-.5-.691-1.025-.683-.558.008-.225.317.009.442.283.158.608.75.683.941.133.376.567 1.092 2.242.784 0 .558.008 1.083.008 1.242 0 .174-.125.374-.458.316a6.662 6.662 0 0 1-4.559-6.325A6.665 6.665 0 0 1 8 1.3Z"/>
</svg>

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,237 @@
#!/usr/bin/env python3
"""
Fetch all PR conversation comments + reviews + review threads (inline threads)
for the PR associated with the current git branch, by shelling out to:
gh api graphql
Requires:
- `gh auth login` already set up
- current branch has an associated (open) PR
Usage:
python fetch_comments.py > pr_comments.json
"""
from __future__ import annotations
import json
import subprocess
import sys
from typing import Any
QUERY = """\
query(
$owner: String!,
$repo: String!,
$number: Int!,
$commentsCursor: String,
$reviewsCursor: String,
$threadsCursor: String
) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
number
url
title
state
# Top-level "Conversation" comments (issue comments on the PR)
comments(first: 100, after: $commentsCursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
body
createdAt
updatedAt
author { login }
}
}
# Review submissions (Approve / Request changes / Comment), with body if present
reviews(first: 100, after: $reviewsCursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
state
body
submittedAt
author { login }
}
}
# Inline review threads (grouped), includes resolved state
reviewThreads(first: 100, after: $threadsCursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
isResolved
isOutdated
path
line
diffSide
startLine
startDiffSide
originalLine
originalStartLine
resolvedBy { login }
comments(first: 100) {
nodes {
id
body
createdAt
updatedAt
author { login }
}
}
}
}
}
}
}
"""
def _run(cmd: list[str], stdin: str | None = None) -> str:
p = subprocess.run(cmd, input=stdin, capture_output=True, text=True)
if p.returncode != 0:
raise RuntimeError(f"Command failed: {' '.join(cmd)}\n{p.stderr}")
return p.stdout
def _run_json(cmd: list[str], stdin: str | None = None) -> dict[str, Any]:
out = _run(cmd, stdin=stdin)
try:
return json.loads(out)
except json.JSONDecodeError as e:
raise RuntimeError(f"Failed to parse JSON from command output: {e}\nRaw:\n{out}") from e
def _ensure_gh_authenticated() -> None:
try:
_run(["gh", "auth", "status"])
except RuntimeError:
print("run `gh auth login` to authenticate the GitHub CLI", file=sys.stderr)
raise RuntimeError("gh auth status failed; run `gh auth login` to authenticate the GitHub CLI") from None
def gh_pr_view_json(fields: str) -> dict[str, Any]:
# fields is a comma-separated list like: "number,headRepositoryOwner,headRepository"
return _run_json(["gh", "pr", "view", "--json", fields])
def get_current_pr_ref() -> tuple[str, str, int]:
"""
Resolve the PR for the current branch (whatever gh considers associated).
Works for cross-repo PRs too, by reading head repository owner/name.
"""
pr = gh_pr_view_json("number,headRepositoryOwner,headRepository")
owner = pr["headRepositoryOwner"]["login"]
repo = pr["headRepository"]["name"]
number = int(pr["number"])
return owner, repo, number
def gh_api_graphql(
owner: str,
repo: str,
number: int,
comments_cursor: str | None = None,
reviews_cursor: str | None = None,
threads_cursor: str | None = None,
) -> dict[str, Any]:
"""
Call `gh api graphql` using -F variables, avoiding JSON blobs with nulls.
Query is passed via stdin using query=@- to avoid shell newline/quoting issues.
"""
cmd = [
"gh",
"api",
"graphql",
"-F",
"query=@-",
"-F",
f"owner={owner}",
"-F",
f"repo={repo}",
"-F",
f"number={number}",
]
if comments_cursor:
cmd += ["-F", f"commentsCursor={comments_cursor}"]
if reviews_cursor:
cmd += ["-F", f"reviewsCursor={reviews_cursor}"]
if threads_cursor:
cmd += ["-F", f"threadsCursor={threads_cursor}"]
return _run_json(cmd, stdin=QUERY)
def fetch_all(owner: str, repo: str, number: int) -> dict[str, Any]:
conversation_comments: list[dict[str, Any]] = []
reviews: list[dict[str, Any]] = []
review_threads: list[dict[str, Any]] = []
comments_cursor: str | None = None
reviews_cursor: str | None = None
threads_cursor: str | None = None
pr_meta: dict[str, Any] | None = None
while True:
payload = gh_api_graphql(
owner=owner,
repo=repo,
number=number,
comments_cursor=comments_cursor,
reviews_cursor=reviews_cursor,
threads_cursor=threads_cursor,
)
if "errors" in payload and payload["errors"]:
raise RuntimeError(f"GitHub GraphQL errors:\n{json.dumps(payload['errors'], indent=2)}")
pr = payload["data"]["repository"]["pullRequest"]
if pr_meta is None:
pr_meta = {
"number": pr["number"],
"url": pr["url"],
"title": pr["title"],
"state": pr["state"],
"owner": owner,
"repo": repo,
}
c = pr["comments"]
r = pr["reviews"]
t = pr["reviewThreads"]
conversation_comments.extend(c.get("nodes") or [])
reviews.extend(r.get("nodes") or [])
review_threads.extend(t.get("nodes") or [])
comments_cursor = c["pageInfo"]["endCursor"] if c["pageInfo"]["hasNextPage"] else None
reviews_cursor = r["pageInfo"]["endCursor"] if r["pageInfo"]["hasNextPage"] else None
threads_cursor = t["pageInfo"]["endCursor"] if t["pageInfo"]["hasNextPage"] else None
if not (comments_cursor or reviews_cursor or threads_cursor):
break
assert pr_meta is not None
return {
"pull_request": pr_meta,
"conversation_comments": conversation_comments,
"reviews": reviews,
"review_threads": review_threads,
}
def main() -> None:
_ensure_gh_authenticated()
owner, repo, number = get_current_pr_ref()
result = fetch_all(owner, repo, number)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,65 @@
---
name: hackage-release
description: Use when user asks to release, publish, or bump version of a Haskell package to Hackage
---
# Hackage Release
Bump version, build, validate, tag, push, and publish a Haskell package to Hackage.
## Workflow
1. **Bump version** in `package.yaml` (if using hpack) or `.cabal` file
2. **Update ChangeLog.md** with release notes
3. **Regenerate cabal** (if using hpack): `hpack`
4. **Build**: `cabal build`
5. **Check**: `cabal check` (must report zero warnings)
6. **Create sdist**: `cabal sdist`
7. **Commit & tag**: commit all changed files, `git tag vX.Y.Z.W`
8. **Push**: `git push && git push --tags`
9. **Get Hackage credentials**: `pass show hackage.haskell.org.gpg`
- Format: first line is password, `user:` line has username
10. **Publish package**: `cabal upload --publish <sdist-tarball> --username=<user> --password='<pass>'`
11. **Build & publish docs**: `cabal haddock --haddock-for-hackage` then `cabal upload --documentation --publish <docs-tarball> --username=<user> --password='<pass>'`
## Version Bumping (PVP)
Haskell uses the [Package Versioning Policy](https://pvp.haskell.org/) with format `A.B.C.D`:
| Component | When to Bump |
|-----------|-------------|
| A.B (major) | Breaking API changes |
| C (minor) | Backwards-compatible new features |
| D (patch) | Bug fixes, non-API changes |
## Nix-Based Projects
If the project uses a Nix flake, wrap cabal commands with `nix develop`:
```bash
nix develop --command cabal build
nix develop --command cabal check
nix develop --command hpack package.yaml
```
Prefer `nix develop` (flake) over `nix-shell` (legacy) to avoid ABI mismatches.
## PVP Dependency Bounds
Hackage warns about:
- **Missing upper bounds**: Every dependency should have an upper bound (e.g., `text >= 1.2 && < 2.2`)
- **Trailing zeros in upper bounds**: Use `< 2` not `< 2.0.0`; use `< 0.4` not `< 0.4.0.0`
Run `cabal check` to verify zero warnings before releasing.
## Checklist
- [ ] Version bumped in package.yaml / .cabal
- [ ] ChangeLog.md updated
- [ ] Cabal file regenerated (if hpack)
- [ ] `cabal build` succeeds
- [ ] `cabal check` reports no errors or warnings
- [ ] Changes committed and tagged
- [ ] Pushed to remote with tags
- [ ] Package published to Hackage
- [ ] Docs published to Hackage

View File

@@ -0,0 +1,32 @@
---
name: journaling
description: Use when user wants to journal, reflect, write a journal entry, or process thoughts. Also use when user mentions wanting to talk through what's on their mind.
---
# Journaling
## Overview
Guide the user through a freeform journaling conversation, then synthesize their thoughts into an organized `.org` file.
## How It Works
**1. Open the conversation.** Ask what's on their mind, how things have been going, or what they want to talk through. Keep it open-ended.
**2. Follow up naturally.** Listen for what seems important - dig into those threads. Don't rush through a checklist. One question at a time.
**3. Synthesize into a journal entry.** When the conversation winds down (or the user says they're done), write an organized `~/org/journal/YYYY-MM-DD.org` file with:
- A timestamp on the first line: `[YYYY-MM-DD Day HH:MM]`
- Org headings that emerge naturally from the conversation topics
- The user's thoughts in their own voice, but organized and cleaned up
- No rigid template - structure follows content
**4. Offer to review.** Show them the entry before writing, let them tweak it.
## Guidelines
- This is their space. Don't coach or advise unless asked.
- Reflect back what you hear - help them see their own patterns.
- If they seem stuck, gently prompt: recent events, feelings, goals, relationships, work.
- Keep the tone warm but not saccharine.
- Entries go in `~/org/journal/` as `YYYY-MM-DD.org`.

View File

@@ -0,0 +1,124 @@
---
name: logical-commits
description: Use when the user asks to split current git changes into logical commits, clean up commit history, create atomic commits, or stage by hunk. Review the whole worktree, group related changes, and produce ordered commits where each commit is a valid state (builds/tests pass with the project validation command).
---
# Logical Commits
Turn a mixed worktree into a clean sequence of atomic commits.
## Workflow
1. Inspect the full change set before staging anything.
2. Define commit boundaries by behavior or concern, not by file count.
3. Order commits so dependencies land first (types/api/schema/helpers before consumers).
4. Stage only the exact hunks for one commit.
5. Validate that staged commit state is healthy before committing.
6. Commit with a precise message.
7. Repeat until all intended changes are committed.
## 1) Inspect First
Run:
```bash
git status --short
git diff --stat
git diff
```
If there are staged changes already, inspect both views:
```bash
git diff --staged
git diff
```
## 2) Choose Validation Command Early
Select the fastest command that proves the repo is valid for this project. Prefer project-standard commands (for example: `just test`, `npm test`, `cargo test`, `go test ./...`, `nix flake check`, targeted build commands).
If no clear command exists:
1. Infer the best available command from repo scripts/config.
2. Tell the user what command you chose and why.
3. Do not claim full validation if coverage is partial.
## 3) Plan the Commit Stack
Before committing, write a short plan:
1. Commit title
2. Files and hunks included
3. Why this is a coherent unit
4. Validation command to run
If changes are intertwined, split by hunk (`git add -p`). If hunk splitting is not enough, use `git add -e` or perform a temporary refactor so each commit remains coherent and valid.
## 4) Stage Exactly One Commit
Preferred staging flow:
```bash
git add -p <file>
git diff --staged
```
Useful corrections:
```bash
git restore --staged -p <file> # unstage specific hunks
git reset -p <file> # alternate unstage flow
```
Never stage unrelated edits just to make the commit pass.
## 5) Validate Before Commit
Run the chosen validation command with the current staged/working tree state.
If validation fails:
1. Fix only what belongs in this logical commit, or
2. Unstage/re-split and revise the commit boundary.
Commit only after validation passes.
## 6) Commit and Verify
Commit:
```bash
git commit -m "<type>: <logical change>"
```
Then confirm:
```bash
git show --stat --oneline -1
```
Ensure remaining unstaged changes still make sense for later commits.
## 7) Final Checks
After finishing the stack:
```bash
git log --oneline --decorate -n <count>
git status
```
Report:
1. The commit sequence created
2. Validation command(s) run per commit
3. Any residual risks (for example, partial validation only)
## Guardrails
1. Keep commits atomic and reviewable.
2. Prefer hunk staging over broad file staging when a file contains multiple concerns.
3. Preserve user changes; do not discard unrelated work.
4. Avoid destructive commands unless the user explicitly requests them.
5. If a clean logical split is impossible without deeper refactor, explain the blocker and ask for direction.

View File

@@ -0,0 +1,77 @@
---
name: nixpkgs-review
description: Review or prepare nixpkgs package changes and PRs using a checklist distilled from review feedback on Ivan Malison's own NixOS/nixpkgs pull requests. Use when working in nixpkgs on package inits, updates, packaging fixes, or before opening or reviewing a nixpkgs PR.
---
# Nixpkgs Review
Use this skill when the task is specifically about reviewing or tightening a change in `NixOS/nixpkgs`.
The goal is not generic style review. The goal is to catch the kinds of issues that repeatedly came up in real nixpkgs feedback on Ivan's PRs: derivation structure, builder choice, metadata, PR hygiene, and JS packaging details.
## Workflow
1. Read the scope first.
Open the changed `package.nix` files, related metadata, and the PR title/body if there is one.
2. Run the historical checklist below.
Bias toward concrete review findings and actionable edits, not abstract style commentary.
3. Validate the package path.
Use the narrowest reasonable validation for the task: targeted build, package eval, or `nixpkgs-review` when appropriate.
4. If you are writing a review:
Lead with findings ordered by severity, include file references, and tie each point to a nixpkgs expectation.
5. If you are preparing a PR:
Fix the checklist items before opening it, then confirm title/body/commit hygiene.
## Historical Checklist
### Derivation structure
- Prefer `finalAttrs` over `rec` for derivations and nested derivations when self-references matter.
- Prefer `tag = "v${...}"` over `rev` when fetching a tagged upstream release.
- Check whether `strictDeps = true;` should be enabled.
- Use the narrowest builder/stdenv that matches the package. If no compiler is needed, consider `stdenvNoCC`.
- Put source modifications in `postPatch` or another appropriate hook, not inside `buildPhase`.
- Prefer `makeBinaryWrapper` over `makeWrapper` when a compiled wrapper is sufficient.
- Keep wrappers aligned with `meta.mainProgram` so overrides remain clean.
- Avoid `with lib;` in package expressions; prefer explicit `lib.*` references.
### Metadata and platform expectations
- For new packages, ensure maintainers are present and include the submitter when appropriate.
- Check whether platform restrictions are justified. Do not mark packages Linux-only or broken without evidence.
- If a package is only workable through patch accumulation and has no maintainer, call that out directly.
### JS, Bun, Electron, and wrapper-heavy packages
- Separate runtime deps from build-only deps. Large closures attract review attention.
- Remove redundant env vars and duplicated configuration if build hooks already cover them.
- Check bundled tool/runtime version alignment, especially browser/runtime pairs.
- Install completions, desktop files, or icons when upstream clearly ships them and the package already exposes the feature.
- Be careful with wrappers that hardcode env vars users may want to override.
### PR hygiene
- PR title should match nixpkgs naming and the package version.
- Keep the PR template intact unless there is a strong reason not to.
- Avoid unrelated commits in the PR branch.
- Watch for duplicate or overlapping PRs before investing in deeper review.
- If asked, squash fixup history before merge.
## Review Output
When producing a review, prefer this shape:
- Finding: what is wrong or risky.
- Why it matters in nixpkgs terms.
- Concrete fix, ideally with the exact attr/hook/builder to use.
If there are no findings, say so explicitly and mention remaining validation gaps.
## References
- Read [references/review-patterns.md](references/review-patterns.md) for the curated list of recurring review themes and concrete PR examples.
- Run `scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8 --limit 20 --format markdown` to refresh the source material from newer PRs.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Nixpkgs Review"
short_description: "Review nixpkgs changes with historical guidance"
default_prompt: "Use $nixpkgs-review to review this nixpkgs package change before I open the PR."

View File

@@ -0,0 +1,105 @@
# Nixpkgs Review Patterns
This reference is a curated summary of recurring feedback from Ivan Malison's `NixOS/nixpkgs` PRs. Use it to ground reviews in patterns that have already come up from nixpkgs reviewers.
## Most Repeated Themes
### 1. Prefer `finalAttrs` over `rec`
This came up repeatedly on both package init and update PRs.
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewer asked for `buildNpmPackage (finalAttrs: { ... })` instead of `rec`.
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`: same feedback for `rustPlatform.buildRustPackage`.
Practical rule:
- If the derivation self-references `version`, `src`, `pname`, `meta.mainProgram`, or nested outputs, default to `finalAttrs`.
### 2. Prefer `tag` when upstream release is a tag
This also repeated across multiple PRs.
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`
Practical rule:
- If upstream publishes a named release tag, prefer `tag = "v${finalAttrs.version}";` or the exact tag format instead of a raw `rev`.
### 3. Use the right hook and builder
Reviewers often push on hook placement and builder/stdenv choice.
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: feedback to move work from `buildPhase` into `postPatch`.
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: feedback to consider `stdenvNoCC`.
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: prefer `makeBinaryWrapper` for a simple wrapper.
Practical rule:
- Check whether each mutation belongs in `postPatch`, `preConfigure`, `buildPhase`, or `installPhase`.
- Check whether the package genuinely needs a compiler toolchain.
- For simple env/arg wrappers, prefer `makeBinaryWrapper`.
### 4. Enable `strictDeps` unless there is a reason not to
This was called out explicitly on [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465).
Practical rule:
- For new derivations, ask whether `strictDeps = true;` should be present.
- If not, be ready to justify why the builder or package layout makes it unnecessary.
### 5. Keep metadata explicit and override-friendly
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewer asked to avoid `with lib;`.
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: reviewer suggested deriving wrapper executable name from `finalAttrs.meta.mainProgram`.
Practical rule:
- Prefer `lib.licenses.mit` over `with lib;`.
- Keep `meta.mainProgram` authoritative and have wrappers follow it when practical.
### 6. Maintainers matter for new packages
- [PR #496806](https://github.com/NixOS/nixpkgs/pull/496806) `gws`: reviewer would not merge until the submitter appeared in maintainers.
Practical rule:
- For package inits, check maintainers early rather than waiting for review feedback.
### 7. PR title and template hygiene are review targets
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: asked to fix the PR title to match the version.
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`: reviewer asked what happened to the PR template.
Practical rule:
- Before opening or updating a PR, verify the title, template, and branch scope.
### 8. Duplicate or overlapping PRs get noticed quickly
- [PR #490227](https://github.com/NixOS/nixpkgs/pull/490227) was replaced by [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230).
- [PR #490053](https://github.com/NixOS/nixpkgs/pull/490053) overlapped with [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033).
- [PR #488606](https://github.com/NixOS/nixpkgs/pull/488606), [PR #488602](https://github.com/NixOS/nixpkgs/pull/488602), and [PR #488603](https://github.com/NixOS/nixpkgs/pull/488603) were closed after reviewers pointed to existing work.
Practical rule:
- Search for existing PRs on the package before spending time polishing a review.
- If a branch contains unrelated commits, fix that before asking for review.
### 9. JS/Bun/Electron packages draw runtime-layout scrutiny
This came up heavily on `t3code` and `playwright-cli`.
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: reviewers proposed trimming the runtime closure, removing unnecessary env vars, and adding shell completions and desktop integration.
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewers called out mismatched bundled `playwright-core` and browser binaries, and wrapper behavior that prevented user overrides.
Practical rule:
- For JS-heavy packages, inspect closure size, runtime vs build-only deps, wrapper env vars, and version alignment between bundled libraries and external binaries.
### 10. Cross-platform evidence helps
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) received an approval explicitly noting Darwin success.
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) got feedback questioning platform restrictions and build behavior.
Practical rule:
- If the package plausibly supports Darwin, avoid premature Linux-only restrictions and mention what was or was not tested.
## How To Use This Reference
- Use these patterns as a focused checklist before submitting or reviewing nixpkgs changes.
- Do not blindly apply every point. Check whether the builder, language ecosystem, and upstream release model actually match.
- When in doubt, prefer concrete evidence from the current package diff over generic convention.

View File

@@ -0,0 +1,2 @@
__pycache__/
*.pyc

View File

@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Mine external feedback from recent GitHub PRs.
Examples:
python scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8
python scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8 --limit 30 --format json
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, as_completed
def run(cmd: list[str]) -> str:
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
raise RuntimeError(proc.stderr.strip() or f"command failed: {' '.join(cmd)}")
return proc.stdout
def gh_json(args: list[str]) -> object:
return json.loads(run(["gh", *args]))
def fetch_prs(repo: str, author: str, limit: int) -> list[dict]:
prs: dict[int, dict] = {}
for state in ("open", "closed"):
data = gh_json(
[
"search",
"prs",
"--repo",
repo,
"--author",
author,
"--limit",
str(max(limit, 30)),
"--state",
state,
"--json",
"number,title,state,closedAt,updatedAt,url",
]
)
for pr in data:
prs[pr["number"]] = pr
return sorted(
prs.values(),
key=lambda pr: (pr["updatedAt"], pr["number"]),
reverse=True,
)[:limit]
def fetch_feedback(repo: str, author: str, pr: dict) -> dict:
owner, name = repo.split("/", 1)
number = pr["number"]
def api(path: str) -> list[dict]:
return gh_json(["api", f"repos/{owner}/{name}/{path}", "--paginate"])
issue_comments = api(f"issues/{number}/comments")
review_comments = api(f"pulls/{number}/comments")
reviews = api(f"pulls/{number}/reviews")
comments = []
for comment in issue_comments:
login = comment["user"]["login"]
body = (comment.get("body") or "").strip()
if login != author and body:
comments.append({"kind": "issue", "user": login, "body": body})
for comment in review_comments:
login = comment["user"]["login"]
body = (comment.get("body") or "").strip()
if login != author and body:
comments.append(
{
"kind": "review_comment",
"user": login,
"body": body,
"path": comment.get("path"),
"line": comment.get("line"),
}
)
for review in reviews:
login = review["user"]["login"]
body = (review.get("body") or "").strip()
if login != author and body:
comments.append(
{
"kind": "review",
"user": login,
"body": body,
"state": review.get("state"),
}
)
return {**pr, "comments": comments}
def is_bot(login: str) -> bool:
return login.endswith("[bot]") or login in {"github-actions", "app/dependabot"}
def render_markdown(results: list[dict], include_bots: bool) -> str:
commenters = Counter()
kept = []
for pr in results:
comments = [
comment
for comment in pr["comments"]
if include_bots or not is_bot(comment["user"])
]
if comments:
kept.append({**pr, "comments": comments})
commenters.update(comment["user"] for comment in comments)
lines = [
"# PR Feedback Summary",
"",
f"- PRs scanned: {len(results)}",
f"- PRs with external feedback: {len(kept)}",
"",
"## Top commenters",
"",
]
for user, count in commenters.most_common(10):
lines.append(f"- `{user}`: {count}")
for pr in kept:
lines.extend(
[
"",
f"## PR #{pr['number']}: {pr['title']}",
"",
f"- URL: {pr['url']}",
f"- State: {pr['state']}",
"",
]
)
for comment in pr["comments"]:
body = comment["body"].replace("\r", " ").replace("\n", " ").strip()
snippet = body[:280] + ("..." if len(body) > 280 else "")
lines.append(f"- `{comment['user']}` `{comment['kind']}`: {snippet}")
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description="Collect review feedback from recent GitHub PRs.")
parser.add_argument("--repo", required=True, help="GitHub repo in owner/name form")
parser.add_argument("--author", required=True, help="PR author to inspect")
parser.add_argument("--limit", type=int, default=20, help="How many recent PRs to inspect")
parser.add_argument(
"--format",
choices=("markdown", "json"),
default="markdown",
help="Output format",
)
parser.add_argument(
"--include-bots",
action="store_true",
help="Keep bot comments in the output",
)
parser.add_argument(
"--workers",
type=int,
default=6,
help="Maximum concurrent GitHub API workers",
)
args = parser.parse_args()
try:
run(["gh", "auth", "status"])
except RuntimeError as err:
print(err, file=sys.stderr)
return 1
prs = fetch_prs(args.repo, args.author, args.limit)
results = []
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = [pool.submit(fetch_feedback, args.repo, args.author, pr) for pr in prs]
for future in as_completed(futures):
results.append(future.result())
results.sort(key=lambda pr: (pr["updatedAt"], pr["number"]), reverse=True)
if args.format == "json":
if not args.include_bots:
for pr in results:
pr["comments"] = [
comment for comment in pr["comments"] if not is_bot(comment["user"])
]
json.dump(results, sys.stdout, indent=2)
sys.stdout.write("\n")
else:
sys.stdout.write(render_markdown(results, args.include_bots))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,51 @@
---
name: org-agenda-api-production
description: Use when investigating production org-agenda-api state, testing endpoints, or debugging production issues
---
# org-agenda-api Production Access
## Overview
Access the production org-agenda-api instance at https://colonelpanic-org-agenda.fly.dev/ for debugging, testing, or verification.
## Credentials
Get the password from `pass`:
```bash
pass show org-agenda-api/imalison
```
Username is currently `imalison`.
## Quick Access with just
This repo includes a `justfile` under `~/dotfiles/org-agenda-api` with pre-configured commands:
```bash
cd ~/dotfiles/org-agenda-api
just health
just get-all-todos
just get-todays-agenda
just agenda
just agenda-files
just todo-states
just create-todo "Test todo"
```
## Manual curl
Prefer using the `just` recipes above so we don't bake auth syntax into docs.
## Key Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| /health | GET | Health check |
| /version | GET | API version |
| /get-all-todos | GET | All TODO items |
| /agenda | GET | Agenda (span=day\|week) |
| /capture | POST | Create entry |
| /update | POST | Update heading |
| /complete | POST | Complete item |
| /delete | POST | Delete heading |

View File

@@ -0,0 +1,312 @@
---
name: org-agenda-api
description: Use when interacting with the org-agenda-api HTTP server to read/write org-mode agenda data
---
# Org Agenda API Reference
HTTP API for org-mode agenda data. Use this skill when you need to query or modify org agenda entries programmatically.
## Authentication
Get credentials from pass:
```bash
pass show colonelpanic-org-agenda.fly.dev
```
Returns: password on first line, then `user:` and `url:` fields.
**Note:** The `url` field in pass may be outdated. Use the base URL below.
## Base URL
`https://colonelpanic-org-agenda.fly.dev`
All requests use Basic Auth with the credentials from pass.
## Read Endpoints
### GET /agenda
Get agenda entries for a day or week.
Query params:
- `span`: `day` (default) or `week`
- `date`: `YYYY-MM-DD` (default: today)
- `include_overdue`: `true` to include overdue items from previous days
- `include_completed`: `true` to include items completed on the queried date
- `refresh`: `true` to git pull repos first
Response includes `span`, `date`, `entries` array, and optionally `gitRefresh` results.
### GET /get-all-todos
Get all TODO items from agenda files.
Query params:
- `refresh`: `true` to git pull first
Response includes `defaults` (with `notifyBefore`), `todos` array, and optionally `gitRefresh`.
### GET /metadata
Get all app metadata in a single request. Returns:
- `templates`: capture templates
- `filterOptions`: tags, categories, priorities, todoStates
- `todoStates`: active and done states
- `customViews`: available custom agenda views
- `errors`: any errors encountered fetching above
### GET /todo-states
Get configured TODO states. Returns:
- `active`: array of not-done states (TODO, NEXT, etc.)
- `done`: array of done states (DONE, CANCELLED, etc.)
### GET /filter-options
Get available filter options. Returns:
- `todoStates`: all states
- `priorities`: available priorities (A, B, C)
- `tags`: all tags from agenda files
- `categories`: all categories
### GET /custom-views
List available custom agenda views. Returns array of `{key, name}` objects.
### GET /custom-view
Run a custom agenda view.
Query params:
- `key` (required): custom agenda command key
- `refresh`: `true` to git pull first
### GET /agenda-files
Get list of org-agenda-files with existence and readability status.
### GET /capture-templates (alias: /templates)
List available capture templates with their prompts.
### GET /health
Health check. Returns `status`, `uptime`, `requests`, and `captureStatus` if unhealthy.
### GET /version
Version info. Returns `version` and `gitCommit`.
### GET /debug-config
Current org configuration for debugging.
## Write Endpoints
### POST /capture
Create a new entry using a capture template.
**Important:** Use `capture-g` (GTD Todo) for most tasks - it properly records creation time and logbook history. Only use `default` when you specifically don't want GTD tracking.
Body:
```json
{
"template": "capture-g",
"values": {
"Title": "Task title",
"scheduled": "2026-01-20",
"deadline": "2026-01-25",
"priority": "A",
"tags": ["work", "urgent"],
"todo": "TODO"
}
}
```
### POST /complete
Mark a TODO as complete.
Body (use any combination to identify the item):
```json
{
"id": "org-id-if-available",
"file": "/path/to/file.org",
"pos": 12345,
"title": "Task title",
"state": "DONE"
}
```
Lookup order: id -> file+pos+title -> file+title -> title only
### POST /update
Update a TODO's scheduled date, deadline, priority, tags, or properties.
Body:
```json
{
"id": "org-id",
"file": "/path/to/file.org",
"pos": 12345,
"title": "Task title",
"scheduled": "2026-01-20T10:00:00",
"deadline": "2026-01-25",
"priority": "B",
"tags": ["updated", "tags"],
"properties": {
"CUSTOM_PROP": "value"
}
}
```
Set value to `null` or empty string to clear. Response includes new `pos` for cache updates.
### POST /delete
Delete an org item permanently.
Body:
```json
{
"id": "org-id",
"file": "/path/to/file.org",
"position": 12345,
"include_children": true
}
```
Requires `include_children: true` if item has children, otherwise returns error.
### POST /restart
Restart the Emacs server (exits gracefully, supervisord restarts).
## Category Strategy Endpoints
These require org-category-capture to be configured.
### GET /category-types
List registered category strategy types. Returns array with:
- `name`: strategy type name
- `hasCategories`: boolean
- `captureTemplate`: template string
- `prompts`: array of prompt definitions
### GET /categories
Get categories for a strategy type.
Query params:
- `type` (required): strategy type name (e.g., "projects")
- `existing_only`: `true` to only return categories with capture locations
Returns `type`, `categories` array, `todoFiles` array.
### GET /category-tasks
Get tasks for a specific category.
Query params:
- `type` (required): strategy type name
- `category` (required): category name
### POST /category-capture
Capture a new entry to a category.
Body:
```json
{
"type": "projects",
"category": "my-project",
"title": "Task title",
"todo": "TODO",
"scheduled": "2026-01-20",
"deadline": "2026-01-25",
"priority": "A",
"tags": ["work"],
"properties": {"EFFORT": "1h"}
}
```
## Response Format
Agenda/todo entries include:
- `todo`: TODO state (TODO, NEXT, DONE, etc.)
- `title`: Heading text
- `scheduled`: ISO date or datetime
- `deadline`: ISO date or datetime
- `priority`: A, B, or C (only if explicitly set)
- `tags`: Array of tags
- `file`: Source file path
- `pos`: Position in file (may change after edits)
- `id`: Org ID if set (stable identifier)
- `olpath`: Outline path array
- `level`: Heading level
- `category`: Category of the item
- `properties`: All properties from the property drawer
- `completedAt`: ISO timestamp when completed (if applicable)
- `agendaLine`: Raw agenda display text (agenda endpoint only)
- `notifyBefore`: Array of minutes for notifications
- `isWindowHabit`: Boolean for window habits
- `habitSummary`: Summary object for habits (if applicable)
## Common Workflows
**View today's agenda:**
```bash
curl -s -u "$USER:$PASS" "$URL/agenda?span=day" | jq '.entries[] | {todo, title, scheduled}'
```
**View this week:**
```bash
curl -s -u "$USER:$PASS" "$URL/agenda?span=week" | jq .
```
**View completed tasks for a specific date:**
```bash
curl -s -u "$USER:$PASS" "$URL/agenda?date=2026-01-17&include_completed=true" | jq '.entries[] | select(.completedAt != null) | {title, completedAt}'
```
**Get all metadata at once:**
```bash
curl -s -u "$USER:$PASS" "$URL/metadata" | jq .
```
**Create a task:**
```bash
curl -s -u "$USER:$PASS" -X POST "$URL/capture" \
-H "Content-Type: application/json" \
-d '{"template":"capture-g","values":{"Title":"New task","scheduled":"2026-01-20"}}'
```
**Complete a task by title:**
```bash
curl -s -u "$USER:$PASS" -X POST "$URL/complete" \
-H "Content-Type: application/json" \
-d '{"title":"Task title"}'
```
**Update a task's schedule:**
```bash
curl -s -u "$USER:$PASS" -X POST "$URL/update" \
-H "Content-Type: application/json" \
-d '{"title":"Task title","scheduled":"2026-01-21T14:00:00"}'
```
**Clear a deadline:**
```bash
curl -s -u "$USER:$PASS" -X POST "$URL/update" \
-H "Content-Type: application/json" \
-d '{"title":"Task title","deadline":null}'
```
**Delete a task:**
```bash
curl -s -u "$USER:$PASS" -X POST "$URL/delete" \
-H "Content-Type: application/json" \
-d '{"title":"Task to delete","file":"/path/to/file.org","position":12345}'
```
## Error Handling
All endpoints return JSON. Errors include:
```json
{
"status": "error",
"message": "Error description"
}
```
Success responses include:
```json
{
"status": "created" | "completed" | "updated",
...additional fields
}
```

View File

@@ -0,0 +1,122 @@
---
name: password-reset
description: Use when the user wants to reset or rotate a website or service password end-to-end, including finding the right `pass` entry, generating a new password with `xkcdpassgen`, retrieving reset emails through `gws gmail` or a local mail CLI, completing the reset in the browser with Chrome DevTools MCP, and updating the password store safely without losing entry metadata.
---
# Password Reset
## Overview
Handle password resets end-to-end. Prefer `gws gmail` for reset-email retrieval, Chrome DevTools MCP for website interaction, and the local `xkcdpassgen` helper for password generation.
## Tool Priorities
- Prefer `gws gmail` over opening Gmail in the browser.
- If `gws` is unavailable, use an installed Gmail CLI or IMAP-based mail tool if one exists locally. Inspect the environment first instead of guessing command names.
- Prefer Chrome DevTools MCP for all browser interaction.
- Use `pass find` and `pass show` before asking the user for credentials or account details.
## Password Generation
The local password generator is `xkcdpassgen`, defined in `dotfiles/lib/functions/xkcdpassgen` and available in shell as an autoloaded function.
```bash
xkcdpassgen <pass-entry-name>
```
Behavior:
- Generates `xkcdpass -n 3 | tr -d ' '` as the base password.
- Appends one uppercase letter, one digit, and one symbol by default.
- Supports:
- `-U` to omit uppercase
- `-N` to omit number
- `-S` to omit symbol
Do not substitute a different password generator ungless the user explicitly asks.
## Safe `pass` Update Pattern
`xkcdpassgen` writes directly to the `pass` entry it is given. Do not run it against the canonical entry before the reset succeeds, because:
- it would overwrite the current password immediately
- it would replace any extra metadata lines in a multiline `pass` entry
Use this pattern instead:
```bash
entry="service/example"
tmp_entry="${entry}-password-reset-tmp"
existing_contents="$(pass show "$entry" 2>/dev/null || true)"
metadata="$(printf '%s\n' "$existing_contents" | tail -n +2)"
xkcdpassgen "$tmp_entry"
new_password="$(pass show "$tmp_entry" | head -1)"
# ... use $new_password in the reset flow ...
if [ -n "$metadata" ]; then
printf '%s\n%s\n' "$new_password" "$metadata" | pass insert -m -f "$entry"
else
printf '%s\n' "$new_password" | pass insert -m -f "$entry"
fi
pass rm -f "$tmp_entry"
```
If the site rejects the password because of policy constraints, keep the canonical entry unchanged, delete or reuse the temp entry, and generate another candidate with different flags only if needed.
## Reset Workflow
1. Identify the account and canonical `pass` entry.
2. Run `pass find <service>` and inspect likely matches with `pass show`.
3. Capture existing metadata before generating a new password.
4. Generate the candidate password into a temporary `pass` entry with `xkcdpassgen`.
5. Start the reset flow in Chrome DevTools MCP:
- navigate to the login or account page
- use the site's "forgot password" flow, or
- sign in and navigate to security settings if the user asked for a rotation rather than a reset
6. Use `gws gmail` to retrieve the reset email when needed:
- search recent mail by sender domain, subject, or reset-related keywords
- open the message and extract the reset link
- navigate to that link in Chrome DevTools MCP
7. Fill the new password from the temporary `pass` entry and complete the form.
8. Verify success:
- confirmation page, or
- successful login with the new password
9. Promote the temp password into the canonical `pass` entry while preserving metadata, then remove the temp entry.
## Email Guidance
Prefer `gws gmail` for reset-email handling. Typical pattern:
- list recent messages with `gws gmail users messages list --params '{"userId":"me","q":"from:service.example newer_than:7d"}'`
- bias toward reset keywords such as `reset`, `password`, `security`, `verify`, or `signin`
- read shortlisted messages with `gws gmail users messages get --params '{"userId":"me","id":"MESSAGE_ID","format":"full"}'` rather than browsing Gmail manually
If `gws` is unavailable, use an installed Gmail CLI or local mail helper only as a fallback. Keep that discovery lightweight and local to the current environment.
## Browser Guidance
Use Chrome DevTools MCP to complete the reset flow directly:
- navigate to the reset or security page
- take snapshots to identify the relevant inputs and buttons
- click, fill, and submit through the site UI
- verify the success state before updating the canonical `pass` entry
Prefer MCP interaction over describing steps for the user to perform manually.
## Credentials And Account Data
- Search `pass` before asking the user for usernames, recovery emails, or OTP-related entries.
- Preserve existing metadata lines in multiline `pass` entries whenever possible.
- Never print the new password in the final response unless the user explicitly asks for it.
## Failure Handling
- If account discovery is ambiguous, ask a short clarifying question only after checking `pass`.
- If the reset email does not arrive, search spam or alternate senders before giving up.
- If login or reset requires another secret that is not in `pass`, then ask the user.
- If the reset flow fails after temp-password generation, leave the canonical entry untouched.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Password Reset"
short_description: "Reset passwords and update pass safely"
default_prompt: "Use $password-reset to reset this account password, complete the browser flow, and update pass safely."

View File

@@ -0,0 +1,402 @@
---
name: planning-coaching
description: Use when helping with daily planning, task prioritization, reviewing agenda, or when user seems stuck on what to do next
---
# Planning Coaching
Help Ivan with planning through question-driven coaching, honest feedback, and data-informed accountability.
## Persistent Files
**IMPORTANT:** Always read these at the start of planning sessions.
### Context File: `/home/imalison/org/planning/context.org`
Persistent context about Ivan's life, goals, struggles, and current focus. Claude maintains this file - update it when:
- Goals or priorities shift
- New patterns emerge
- Life circumstances change
- We learn something about what helps/doesn't help
Read this first. It's the "state of Ivan" that persists across sessions.
### Daily Journals: `/home/imalison/org/planning/dailies/YYYY-MM-DD.org`
One file per day we do planning. Contains:
- That day's plan (short list, focus areas)
- Stats table from the previous day review (inline)
- Notes from the session
- End-of-day reflection (if we do one)
Create a new file for each planning session day. Reference past dailies to see patterns.
### Stats File: `/home/imalison/org/planning/stats.org`
Running tables for trend analysis:
- **Daily Log**: One row per planning day with all metrics
- **Weekly Summary**: Aggregated weekly totals with notes
### Raw Logs: `/home/imalison/org/planning/logs.jsonl`
Detailed machine-readable log (one JSON object per line, per day). Captures full task data so we can calculate new metrics retroactively.
Each line contains:
```json
{
"date": "2026-01-20",
"planned": [{"title": "...", "friction": 3, "effort": 2, "id": "...", "file": "...", ...}],
"completed": [{"title": "...", "friction": 3, "effort": 2, "completedAt": "...", ...}],
"rescheduled": [{"title": "...", "from": "2026-01-20", "to": "2026-01-21", ...}],
"context": {"energy": "medium", "available_time": "full day", "notes": "..."}
}
```
When recording stats:
1. Append full JSON object to logs.jsonl
2. Add summary row to stats.org Daily Log table
3. Include inline stats table in that day's journal
4. Update Weekly Summary when a week ends
## Core Principles
1. **Question-driven**: Ask questions to help think through priorities rather than dictating
2. **Direct and honest**: Call out avoidance patterns directly - this is wanted
3. **Data-informed**: Use org-agenda-api to look at patterns, velocity, scheduling history
4. **Balance pressure**: Push on procrastination but don't overwhelm on decision-heavy tasks
5. **Lightweight and flexible**: Always offer option to skip parts if not feeling it
6. **No guilt**: If we fall off the wagon, make it easy and encouraging to get back on
## Planning Session Flow
```dot
digraph planning_session {
rankdir=TB;
"Read context.org" [shape=box];
"Yesterday review (skippable)" [shape=box];
"Capture new items" [shape=box];
"Check current state" [shape=box];
"Inbox processing (skippable)" [shape=box];
"Pick focus areas" [shape=box];
"Create short list" [shape=box];
"Meta check (optional)" [shape=box];
"Write daily journal" [shape=box];
"Read context.org" -> "Yesterday review (skippable)";
"Yesterday review (skippable)" -> "Capture new items";
"Capture new items" -> "Check current state";
"Check current state" -> "Inbox processing (skippable)";
"Inbox processing (skippable)" -> "Pick focus areas";
"Pick focus areas" -> "Create short list";
"Create short list" -> "Meta check (optional)";
"Meta check (optional)" -> "Write daily journal";
}
```
Every step marked "skippable" - offer it, but accept "let's skip that today" without question.
### 0. Read Context (Always)
Read `/home/imalison/org/planning/context.org` first. This grounds the session in what's currently going on.
### 1. Yesterday Review (Skippable)
Quick look back at the previous day. Keep it lightweight - a minute or two, not an interrogation.
**Subjective check-in:**
- "How do you feel about yesterday?" (open-ended, not demanding)
- "Anything you want to talk about - productivity or otherwise?"
**Objective stats (if wanted):**
- Completion rate: X of Y planned tasks done
- Friction conquered: total/average friction of completed tasks
- Rescheduled: N tasks bumped to today
- Effort accuracy: any tasks that took way more/less than estimated?
**Keep it encouraging:**
- Celebrate wins, especially high-friction completions
- If it was a rough day, acknowledge it without judgment
- "Yesterday was yesterday. What do we want today to look like?"
**If we haven't done this in a while:**
- "Hey, we haven't done a planning session in [X days]. No big deal - want to ease back in?"
- Don't guilt trip. Just pick up where we are.
### 2. Capture New Items
Before diving into today's state, ask: "Anything new come up that needs to be captured?"
- New tasks, ideas, commitments that surfaced since last session
- Things remembered overnight or during the day
- Add these to org before continuing
**Which capture command to use:**
- `just inbox "Task title"` - Default for new todos. Quick capture without setting properties. Items go to inbox for later triage (setting effort, friction, priority, category).
- `just capture "Task title"` - Only when we're setting effort, friction, priority, or category upfront during the planning session.
This prevents things from falling through the cracks and clears mental load before planning.
### 3. Check Current State
Ask about:
- Energy level right now (low/medium/high)
- Time available and structure of the day
- Any hard deadlines or commitments
- Mental state (scattered? focused? anxious?)
### 4. Inbox Processing (Skippable)
Process items captured to inbox since last session. These are quick captures (`just inbox`) that need triage.
**For each inbox item, decide:**
1. Is this actually actionable? (If not: delete, or convert to reference/someday)
2. Assign FRICTION and EFFORT estimates
3. Set priority if obvious
4. Schedule if it has a natural date, otherwise leave unscheduled for later prioritization
5. **IMPORTANT: Transition state from INBOX to NEXT** using `just set-state "Task title" "NEXT"`
**Process for property assignment:**
1. Both of us estimate FRICTION and EFFORT
2. Use Ivan's values unless we differ by 2+ points
3. If discrepancy >= 2, discuss: "I estimated this as [X] because [reason] - what makes you see it as [Y]?"
**Why this matters:** Items sitting in inbox create mental overhead. Regular processing keeps the system trustworthy.
### 5. Pick Focus Areas
Based on energy and context, choose what *types* of work to tackle:
- High friction tasks (if energy supports it)
- Quick wins (if need momentum)
- Deep work (if have focus time)
- Admin/shallow work (if low energy)
### 6. Create Short List
Curate 3-5 tasks that match the day's reality. Not a full dump - a focused list.
### 7. Meta Check (Optional)
Occasionally (weekly-ish, or when it feels right), ask:
- "Is this planning process working for you?"
- "Anything we should change about how we do this?"
- "Are the FRICTION/EFFORT scales making sense?"
This is how we iterate on the system itself.
## Task Properties
Store in org properties drawer via `just update` with a `properties` field in the JSON body.
### FRICTION (0-5)
Psychological resistance / avoidance tendency / decision paralysis factor.
| Value | Meaning |
|-------|---------|
| 0 | No friction - could start right now |
| 1 | Minimal - minor reluctance |
| 2 | Some - need to push a bit |
| 3 | Moderate - will procrastinate without intention |
| 4 | High - significant avoidance |
| 5 | Maximum - dread/paralysis |
### EFFORT (Fibonacci: 1, 2, 3, 5, 8)
Time/energy investment. Store as number, discuss as t-shirt size.
| Number | T-shirt | Meaning |
|--------|---------|---------|
| 1 | XS | Trivial, <30min |
| 2 | S | Small, ~1-2h |
| 3 | M | Medium, half-day |
| 5 | L | Large, full day |
| 8 | XL | Multi-day effort |
### Setting Properties
```bash
just update '{"title": "Task name", "properties": {"FRICTION": "3", "EFFORT": "5"}}'
```
## Priority Framework
When helping decide what to work on, weigh these factors:
1. **Energy/context match**: Does current energy support this task's friction level?
2. **Deadlines**: What's due soon or has external pressure?
3. **Impact**: What moves the needle most?
High-friction + high-impact tasks need the right conditions. Don't push these when energy is low.
## Handling Avoidance
**Be direct.** Ivan wants honest feedback.
When noticing avoidance patterns:
- "You've rescheduled X three times now. What's making this hard?"
- "This has been on your list for two weeks. Let's talk about what's blocking it."
- "I notice you keep picking small tasks over [big important thing]. What would make that more approachable?"
**Use data:**
- Look at scheduling history via `just agenda-day YYYY-MM-DD`
- Track how long tasks have been scheduled
- Notice patterns in what gets done vs. avoided
## Coaching Stance
**Do:**
- Ask "what's making this hard?" not "why haven't you done this?"
- Offer to break down high-friction tasks into smaller steps
- Notice and celebrate progress, especially on hard things
- Be honest about patterns you see
**Don't:**
- Overwhelm with too many decisions at once
- Push high-friction tasks when energy is clearly low
- Judge - observe and inquire instead
- Let things slide without comment (directness is wanted)
## Red Flags to Watch For
- Same task rescheduled 3+ times
- Consistently avoiding a category of work
- Taking on new commitments while existing ones slip
- Only doing low-friction tasks day after day
- Overcommitting (too many items scheduled for one day)
When you see these: name it directly and explore what's going on.
## Mid-Day Check-ins
These can happen impromptu - not every day, just when useful.
**When to offer:**
- If morning plan isn't working out
- Energy shifted significantly
- Got stuck or derailed
- Finished the short list early
**Keep it brief:**
- "How's it going with [today's focus]?"
- "Want to adjust the plan for the afternoon?"
- "Anything blocking you right now?"
## Metrics We Track
For the daily review, pull these from the API:
| Metric | How to calculate | Why it matters |
|--------|------------------|----------------|
| Completion rate | completed / planned for day | Overall follow-through |
| Friction conquered | sum of FRICTION on completed tasks | Are we tackling hard things? |
| Rescheduling count | tasks that moved from yesterday to today | Chronic rescheduling = avoidance |
| Effort accuracy | compare EFFORT estimate vs actual | Calibrate future estimates |
**Don't obsess over numbers.** They're conversation starters, not report cards.
## Queries for Planning
Use the `just` commands in `/home/imalison/org/justfile` for all API interactions.
**Tasks needing property assignment:**
```bash
just todos # Get all todos, filter for missing FRICTION or EFFORT in properties
```
**Today's agenda (including overdue):**
```bash
just agenda-overdue # Use this for planning - shows today + all overdue items
just agenda # Only today's scheduled items (misses overdue tasks)
```
**Note:** Always use `agenda-overdue` during planning sessions to see the full picture of what needs attention.
**Agenda for specific date:**
```bash
just agenda-day 2026-01-20
```
**Completed items for a specific date:**
```bash
just completed 2026-01-22 # Get items completed on a specific date
just completed-today # Get items completed today
```
**This week's agenda:**
```bash
just agenda-week
```
**Overdue/rescheduled items:**
```bash
just agenda-overdue
```
**Capture new items:**
```bash
just inbox "New task title" # Quick capture to inbox (default)
just capture "Task title" "2026-01-22" # With scheduling
```
**Update task properties:**
```bash
just update '{"title": "Task name", "properties": {"FRICTION": "3", "EFFORT": "5"}}'
```
**Reschedule a task:**
```bash
just reschedule "Task title" "2026-01-25"
```
**Complete a task:**
```bash
just complete "Task title"
```
**Change task state (e.g., INBOX -> NEXT):**
```bash
just set-state "Task title" "NEXT"
```
## Daily Journal Template
Create `/home/imalison/org/planning/dailies/YYYY-MM-DD.org` for each session:
```org
#+TITLE: Planning - YYYY-MM-DD
#+DATE: [YYYY-MM-DD Day]
* Yesterday Review
** Stats
| Metric | Value |
|-------------+-------|
| Planned | N |
| Completed | N |
| Rate | N% |
| Friction | N |
| Rescheduled | N |
** Reflection
[How Ivan felt about yesterday, anything discussed]
* Today's Context
- Energy: [low/medium/high]
- Available time: [description]
- Mental state: [notes]
* Focus Areas
- [What types of work we're tackling today]
* Today's Short List
Use org ID links to reference tasks - don't duplicate task definitions here.
- [[id:uuid-here][Task 1 title]]
- [[id:uuid-here][Task 2 title]]
- [[id:uuid-here][Task 3 title]]
* Notes
[Anything else from the session]
* End of Day (optional)
[If we do an evening check-in]
```
**Also add row to** `/home/imalison/org/planning/stats.org` Daily Log table.
## Updating Context File
Update `/home/imalison/org/planning/context.org` when:
- Ivan mentions a new goal or project
- We notice a recurring pattern
- Something significant changes in life/work
- We discover what helps or doesn't help
- The meta check reveals process adjustments
Don't ask permission to update it - just do it and mention what changed.

View File

@@ -0,0 +1,47 @@
---
name: playwright-cli
description: Automate browser interactions from the shell using Playwright via the `playwright-cli` command (open/goto/snapshot/click/type/screenshot, tabs/storage/network). Use when you need deterministic browser automation for web testing, form filling, screenshots/PDFs, or data extraction.
---
# Browser Automation With playwright-cli
This system provides `playwright-cli` via Nix (see `nixos/flake.nix` for the nixpkgs PR patch and `nixos/code.nix` for installation), so its available on `PATH` without any `npm -g` installs.
## Quick Start
```bash
# First run (downloads browser bits used by Playwright)
playwright-cli install-browser
# Open a new browser session (optionally with a URL)
playwright-cli open
playwright-cli open https://example.com/
# Navigate, inspect, and interact
playwright-cli goto https://playwright.dev
playwright-cli snapshot
playwright-cli click e15
playwright-cli type "search query"
playwright-cli press Enter
# Save artifacts
playwright-cli screenshot --filename=page.png
playwright-cli pdf --filename=page.pdf
# Close the browser
playwright-cli close
```
## Practical Workflow
1. `playwright-cli open` (or `open <url>`)
2. `playwright-cli snapshot`
3. Use element refs (`e1`, `e2`, ...) from the snapshot with `click`, `fill`, `hover`, `check`, etc.
4. Take `screenshot`/`pdf` as needed
5. `playwright-cli close`
## Tips
- Use `playwright-cli state-save auth.json` / `state-load auth.json` to persist login state across runs.
- Use named sessions with `-s=mysession` when you need multiple concurrent browsers.
- Set `PLAYWRIGHT_CLI_PACKAGE` to pin the npm package (default is `@playwright/cli@latest`).

View File

@@ -0,0 +1,5 @@
interface:
display_name: "Playwright CLI"
short_description: "Automate browser interactions"
default_prompt: "Use playwright-cli to automate browser actions (open/goto/snapshot/click/type/screenshot) and save useful artifacts (screenshots, PDFs, auth state)."

View File

@@ -0,0 +1,54 @@
---
name: release
description: Use when user asks to release, publish, bump version, or prepare a new version for deployment
---
# Release
Validate, format, bump version, and tag for release.
## Workflow
1. **Validate** - Run project's validation command
2. **Fix formatting** - Auto-fix prettier/formatting issues if any
3. **Bump version** - Ask user for bump type, update package.json
4. **Commit & tag** - Commit version bump, create git tag
5. **Optionally push** - Ask if user wants to push
## Commands
```bash
# 1. Validate
yarn validate # or: npm run validate
# 2. Fix formatting if needed
yarn prettier:fix # or: npm run prettier:fix
# 3. Bump version (edit package.json)
# patch: 1.2.3 → 1.2.4
# minor: 1.2.3 → 1.3.0
# major: 1.2.3 → 2.0.0
# 4. Commit and tag
git add package.json
git commit -m "chore: bump version to X.Y.Z"
git tag vX.Y.Z
# 5. Push (if requested)
git push && git push --tags
```
## Quick Reference
| Bump Type | When to Use |
|-----------|-------------|
| patch | Bug fixes, small changes |
| minor | New features, backwards compatible |
| major | Breaking changes |
## Before Release Checklist
- [ ] All tests pass
- [ ] No lint errors
- [ ] Formatting is clean
- [ ] Changes are committed

View File

@@ -0,0 +1,86 @@
---
name: taffybar-ecosystem-release
description: Use when releasing, version-bumping, or propagating changes across taffybar GitHub org packages (taffybar, gtk-sni-tray, gtk-strut, status-notifier-item, dbus-menu, dbus-hslogger)
---
# Taffybar Ecosystem Release
Release and propagate changes across the taffybar Haskell package ecosystem.
See also: `taffybar-nixos-flake-chain` for how these packages are consumed by the NixOS configuration and what flake.lock updates may be needed after a release.
## Package Dependency Graph
```
taffybar
├── gtk-sni-tray
│ ├── dbus-menu
│ ├── gtk-strut
│ └── status-notifier-item
├── dbus-menu
├── gtk-strut
├── status-notifier-item
└── dbus-hslogger
```
**Leaf packages** (no ecosystem deps): `gtk-strut`, `status-notifier-item`, `dbus-hslogger`, `dbus-menu`
**Mid-level**: `gtk-sni-tray` (depends on dbus-menu, gtk-strut, status-notifier-item)
**Top-level**: `taffybar` (depends on all above)
## Repositories & Local Checkouts
| Package | GitHub | Local Checkout |
|---------|--------|---------------|
| taffybar | taffybar/taffybar | `~/.config/taffybar/taffybar/` |
| gtk-sni-tray | taffybar/gtk-sni-tray | `~/Projects/gtk-sni-tray/` |
| gtk-strut | taffybar/gtk-strut | `~/Projects/gtk-strut/` |
| status-notifier-item | taffybar/status-notifier-item | `~/Projects/status-notifier-item/` |
| dbus-menu | taffybar/dbus-menu | `~/Projects/dbus-menu/` |
| dbus-hslogger | IvanMalison/dbus-hslogger | `~/Projects/dbus-hslogger/` |
## Releasing a Package
Always release leaf packages before their dependents. Changes propagate **upward** through the graph.
### 1. Release the Changed Package
Use the `hackage-release` skill for the full Hackage publish workflow. In the local checkout:
1. Bump version in `.cabal` file (PVP: A.B.C.D)
2. Update ChangeLog.md
3. `cabal build && cabal check`
4. `cabal sdist`
5. Commit, tag `vX.Y.Z.W`, push with tags
6. Publish to Hackage
7. Publish docs
**Manual doc upload required for GTK-dependent packages:** Hackage cannot build documentation for packages that depend on GTK/GI libraries (the build servers lack the system dependencies). This affects `taffybar`, `gtk-sni-tray`, `gtk-strut`, and `dbus-menu`. For these packages you must build haddocks locally and upload them yourself — see the `hackage-release` skill for the `cabal haddock --haddock-for-hackage` and `cabal upload --documentation` commands. Only `status-notifier-item` and `dbus-hslogger` (pure DBus/Haskell deps) can have their docs built by Hackage automatically.
### 2. Update Dependents' Version Bounds
For each package higher in the graph that depends on what you just released, update the dependency bound in its `.cabal` file. For example, if you bumped `gtk-strut` to 0.1.5.0:
- In `gtk-sni-tray.cabal`: update `gtk-strut >= 0.1.5 && < 0.2`
- In `taffybar.cabal`: update `gtk-strut >= 0.1.5 && < 0.2`
Then release those packages too if needed (repeat from step 1).
### 3. Update Flake Inputs
Each package's `flake.nix` references its ecosystem dependencies as inputs (typically `flake = false` pointing at GitHub). After pushing changes, update the flake.lock in any repo that directly references the changed package:
```bash
cd ~/Projects/gtk-sni-tray # if it depends on what changed
nix flake update gtk-strut
```
```bash
cd ~/.config/taffybar/taffybar # taffybar references all ecosystem pkgs
nix flake update gtk-strut
```
### Full Ecosystem Release Order
1. `gtk-strut`, `status-notifier-item`, `dbus-hslogger`, `dbus-menu` (leaves — parallel OK)
2. `gtk-sni-tray` (update bounds for any leaf changes first)
3. `taffybar` (update bounds for all changes)

View File

@@ -0,0 +1,61 @@
---
name: taffybar-nixos-flake-chain
description: Use when doing NixOS rebuilds involving taffybar, or when flake.lock updates are needed after changing taffybar ecosystem packages. Also use when debugging stale taffybar versions after `just switch`.
---
# Taffybar NixOS Flake Chain
How the taffybar ecosystem packages are consumed by the NixOS configuration through a chain of nested flakes, and what flake.lock updates may be needed when something changes.
See also: `taffybar-ecosystem-release` for the package dependency graph, release workflow, and Hackage publishing.
## The Three-Layer Flake Chain
The NixOS system build pulls in taffybar through three nested flake.nix files:
```
nixos/flake.nix (top — `just switch` reads this)
│ ├── taffybar path:.../taffybar/taffybar
│ ├── imalison-taffybar path:../dotfiles/config/taffybar
│ └── gtk-sni-tray, gtk-strut, etc. (GitHub inputs)
dotfiles/config/taffybar/flake.nix (middle — imalison-taffybar config)
│ ├── taffybar path:.../taffybar/taffybar
│ └── gtk-sni-tray, gtk-strut, etc. (GitHub inputs)
dotfiles/config/taffybar/taffybar/flake.nix (bottom — taffybar library)
│ └── gtk-sni-tray, gtk-strut, etc. (flake = false GitHub inputs)
```
All three flakes declare their own top-level inputs for the ecosystem packages and use `follows` to keep versions consistent within each layer.
## Why Bottom-Up Updates Matter
`path:` inputs snapshot the target flake **including its flake.lock** at lock time. If you only run `nix flake update` at the top (nixos) layer, the middle and bottom layers keep whatever was previously locked in their own flake.lock files.
So when propagating a change to a system rebuild, you generally need to update flake.lock files from the bottom up — the bottom layer first so the middle layer picks up fresh locks when it re-resolves, then the middle so the top picks up fresh locks.
```bash
# Bottom (if an ecosystem dep changed):
cd ~/.config/taffybar/taffybar && nix flake update <pkg>
# Middle:
cd ~/.config/taffybar && nix flake update <pkg> taffybar
# Top:
cd ~/dotfiles/nixos && nix flake update <pkg> imalison-taffybar taffybar
```
Not every change requires touching all three layers. Think about which flake.lock files actually contain stale references:
- Changed **taffybar itself** — it's the bottom layer, so start at the middle (`nix flake update taffybar`) then the top.
- Changed a **leaf ecosystem package** (e.g. gtk-strut) — start at the bottom since taffybar's flake.lock references it, then cascade up.
- The nixos flake also has **direct GitHub inputs** for ecosystem packages with `follows` overrides. Updating those at the top level may be sufficient if nothing changed in the middle/bottom flake.lock files themselves.
## Rebuilding
```bash
cd ~/dotfiles/nixos && just switch
```
If taffybar seems stale after a rebuild, check whether the flake.lock at each layer actually points at the expected revision — a missed cascade step is the usual cause.

View File

@@ -31,7 +31,6 @@
"iterm2",
"java",
"jumpcut",
"karabiner",
"libreoffice",
"macpass",
"mirrordisplays",
@@ -170,6 +169,7 @@
"tig",
"tmate",
"tmux",
"zellij",
"unoconv",
"vim",
"w3m",

6
dotfiles/claude/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*
!.gitignore
!CLAUDE.md
!settings.json
!settings.local.json
!settings.local.json.example

View File

@@ -1,8 +1,4 @@
{
"enabledPlugins": {
"superpowers@superpowers-marketplace": true,
"agent-browser@agent-browser": true
},
"hooks": {
"UserPromptSubmit": [
{
@@ -14,5 +10,11 @@
]
}
]
}
},
"enabledPlugins": {
"superpowers@superpowers-marketplace": true,
"agent-browser@agent-browser": true
},
"effortLevel": "high",
"skipDangerousModePermissionPrompt": true
}

View File

@@ -9,21 +9,11 @@
"mcp": {
"servers": {
"gitea-mcp": {
"command": "docker",
"command": "bash",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITEA_ACCESS_TOKEN=ad1e513137d5a15b638f7b4b5f65e1bda1c70cd7",
"-e",
"GITEA_BASE_URL=https://dev.railbird.ai",
"docker.gitea.com/gitea-mcp-server"
],
"env": {
"GITEA_ACCESS_TOKEN": "ad1e513137d5a15b638f7b4b5f65e1bda1c70cd7",
"GITEA_BASE_URL": "https://dev.railbird.ai"
}
"-lc",
"set -euo pipefail; export GITEA_BASE_URL='https://dev.railbird.ai'; export GITEA_ACCESS_TOKEN=\"$(pass show claude-mcp/gitea-access-token | head -1)\"; exec docker run -i --rm -e GITEA_ACCESS_TOKEN -e GITEA_BASE_URL docker.gitea.com/gitea-mcp-server"
]
},
"chrome-devtools": {
"command": "npx",
@@ -33,13 +23,11 @@
]
},
"imap-email": {
"command": "npx",
"args": ["-y", "imap-email-mcp"],
"env": {
"IMAP_USER": "IvanMalison@gmail.com",
"IMAP_PASSWORD": "iiobqyuuczibsnmi",
"IMAP_HOST": "imap.gmail.com"
}
"command": "bash",
"args": [
"-lc",
"set -euo pipefail; export IMAP_USER='IvanMalison@gmail.com'; export IMAP_HOST='imap.gmail.com'; export IMAP_PASSWORD=\"$(pass show claude-mcp/gmail-imap-app-password | head -1)\"; exec npx -y imap-email-mcp"
]
}
}
},

View File

@@ -0,0 +1,43 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(cat:*)"
],
"deny": []
},
"mcp": {
"servers": {
"gitea-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITEA_ACCESS_TOKEN",
"-e",
"GITEA_BASE_URL=https://dev.railbird.ai",
"docker.gitea.com/gitea-mcp-server"
]
},
"chrome-devtools": {
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--auto-connect"
]
},
"imap-email": {
"command": "npx",
"args": ["-y", "imap-email-mcp"],
"env": {}
}
}
},
"enabledMcpjsonServers": [
"chrome-devtools",
"imap-email"
],
"enableAllProjectMcpServers": true
}

5
dotfiles/codex/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*
!.gitignore
!AGENTS.md
!config.toml
!skills

View File

@@ -1,6 +1,10 @@
model = "gpt-5.2-codex"
model = "gpt-5.5"
model_reasoning_effort = "high"
personality = "pragmatic"
notify = ["/Users/kat/dotfiles/dotfiles/codex/plugins/cache/openai-bundled/computer-use/1.0.755/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
[projects."/home/imalison/Projects/nixpkgs"]
trust_level = "trusted"
@@ -31,6 +35,108 @@ trust_level = "untrusted"
[projects."/home/imalison/org"]
trust_level = "trusted"
[projects."/home/imalison/dotfiles/.git/modules/dotfiles/config/taffybar"]
trust_level = "trusted"
[projects."/home/imalison/Projects/notifications-tray-icon"]
trust_level = "trusted"
[projects."/home/imalison/Projects/hyprland"]
trust_level = "trusted"
[projects."/home/imalison/Projects/git-sync-rs"]
trust_level = "trusted"
[projects."/home/imalison/Projects/keepbook"]
trust_level = "trusted"
[projects."/home/imalison/Projects/boxcars"]
trust_level = "trusted"
[projects."/home/imalison/Projects/rumno"]
trust_level = "trusted"
[projects."/home/imalison/Projects/git-blame-rank"]
trust_level = "trusted"
[projects."/home/imalison/Projects/hatchet"]
trust_level = "trusted"
[projects."/home/imalison/dotfiles/dotfiles/emacs.d/elpaca/sources/org-project-capture"]
trust_level = "trusted"
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar/packages"]
trust_level = "trusted"
[projects."/home/imalison/Projects/scrobble-tools"]
trust_level = "trusted"
[projects."/home/imalison/.password-store"]
trust_level = "trusted"
[projects."/home/imalison/Projects/subtr-actor-mechanics"]
trust_level = "trusted"
[projects."/home/imalison/Projects/lastfm-edit"]
trust_level = "trusted"
[projects."/home/imalison/Projects/mova"]
trust_level = "trusted"
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"]
trust_level = "trusted"
[projects."/home/imalison/Projects"]
trust_level = "trusted"
[projects."/home/imalison/Projects/rofi-systemd"]
trust_level = "trusted"
[projects."/home/imalison/Projects/map-quiz"]
trust_level = "trusted"
[projects."/run/media/imalison/NETDEBUGUSB"]
trust_level = "trusted"
[projects."/home/imalison/Projects/coqui-tts-streamer"]
trust_level = "trusted"
[projects."/home/imalison/Downloads"]
trust_level = "trusted"
[projects."/home/imalison/keysmith_generated"]
trust_level = "trusted"
[projects."/run/media/imalison/NIXOS_SD"]
trust_level = "trusted"
[projects."/Users/kat/dotfiles"]
trust_level = "trusted"
[projects."/Users/kat"]
trust_level = "trusted"
[projects."/Users/kat/org"]
trust_level = "trusted"
[projects."/Users/kat/Documents/Codex/2026-04-25/do-you-see-the-sandisk-external"]
trust_level = "trusted"
[projects."/Volumes/Extreme SSD/Projects/keepbook"]
trust_level = "trusted"
[projects."/Users/kat/Documents/Codex/2026-04-25/it-seems-like-maybe-we-dont"]
trust_level = "trusted"
[projects."/Users/kat/Documents/Codex/2026-04-25/what-is-the-state-of-tiling"]
trust_level = "trusted"
[projects."/home/imalison/Pictures/ai/2026/celeb"]
trust_level = "trusted"
[projects."/home/imalison/.local/share/keepbook"]
trust_level = "trusted"
[notice]
hide_gpt5_1_migration_prompt = true
"hide_gpt-5.1-codex-max_migration_prompt" = true
@@ -46,11 +152,50 @@ args = ["-y", "chrome-devtools-mcp@latest", "--auto-connect"]
command = "npx"
args = ["-y", "@google-cloud/observability-mcp"]
[mcp_servers.gmail]
command = "nix"
args = ["run", "/home/imalison/Projects/gmail-mcp#gmail-mcp-server"]
[mcp_servers.openaiDeveloperDocs]
url = "https://developers.openai.com/mcp"
[features]
unified_exec = true
apps = true
steer = true
[marketplaces.openai-bundled]
last_updated = "2026-04-21T17:43:57Z"
source_type = "local"
source = "/Users/kat/.codex/.tmp/bundled-marketplaces/openai-bundled"
[marketplaces.openai-primary-runtime]
last_updated = "2026-04-25T23:49:36Z"
source_type = "local"
source = "/Users/kat/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"
[plugins."google-calendar@openai-curated"]
enabled = true
[plugins."gmail@openai-curated"]
enabled = true
[plugins."google-drive@openai-curated"]
enabled = true
[plugins."github@openai-curated"]
enabled = true
[plugins."computer-use@openai-bundled"]
enabled = true
[plugins."documents@openai-primary-runtime"]
enabled = true
[plugins."spreadsheets@openai-primary-runtime"]
enabled = true
[plugins."presentations@openai-primary-runtime"]
enabled = true
[plugins."browser-use@openai-bundled"]
enabled = true
[tui.model_availability_nux]
"gpt-5.5" = 4

1
dotfiles/codex/skills Symbolic link
View File

@@ -0,0 +1 @@
../agents/skills

View File

@@ -1,2 +0,0 @@
[api]
token = 417ba97c-b532-4e4b-86df-a240314ae840

View File

@@ -0,0 +1,18 @@
output HDMI-0
off
output DP-1
off
output DP-2
off
output DP-3
off
output DP-4
off
output DP-5
off
output DP-0
crtc 0
mode 3440x1440
pos 0x0
rate 240.00
x-prop-non_desktop 0

View File

@@ -0,0 +1 @@
DP-0 00ffffffffffff003669d04d0000000033210104b55022783bac05b04d3db7250f5054bfcf00714f81c0814081809500b300d1c00101e77c70a0d0a0295030203a0020513100001a023a801871382d40582c450020513100001e000000fd0c30f0919196010a202020202020000000fc004d50473334314358204f4c45440257020339f14901030204901211133f2309070783010000e2002a741a0000030330f000a066024f03f0000000000000e305e201e6060701664b00565e00a0a0a029503020350020513100001a6fc200a0a0a055503020350020513100001a00000000000000000000000000000000000000000000000000000000000000000000fc7012790300030150a2e300086f0d9f002f801f009f05b20031000900520101086f0d9f002f801f009f05540002000900b76901086f0d9f002f801f009f057600020009006f0502086f0d8f002f801f009f0563001d00090000000000000000000000000000000000000000000000000000000000000000000000000000001590

View File

@@ -1,90 +0,0 @@
@binding-set gtk-emacs-text-entry
{
bind "<ctrl>b" { "move-cursor" (logical-positions, -1, 0) };
bind "<shift><ctrl>b" { "move-cursor" (logical-positions, -1, 1) };
bind "<ctrl>f" { "move-cursor" (logical-positions, 1, 0) };
bind "<shift><ctrl>f" { "move-cursor" (logical-positions, 1, 1) };
bind "<alt>b" { "move-cursor" (words, -1, 0) };
bind "<shift><alt>b" { "move-cursor" (words, -1, 1) };
bind "<alt>f" { "move-cursor" (words, 1, 0) };
bind "<shift><alt>f" { "move-cursor" (words, 1, 1) };
bind "<ctrl>a" { "move-cursor" (paragraph-ends, -1, 0) };
bind "<shift><ctrl>a" { "move-cursor" (paragraph-ends, -1, 1) };
bind "<ctrl>e" { "move-cursor" (paragraph-ends, 1, 0) };
bind "<shift><ctrl>e" { "move-cursor" (paragraph-ends, 1, 1) };
bind "<ctrl>w" { "cut-clipboard" () };
bind "<ctrl>y" { "paste-clipboard" () };
bind "<ctrl>d" { "delete-from-cursor" (chars, 1) };
bind "<alt>d" { "delete-from-cursor" (word-ends, 1) };
bind "<alt>BackSpace" { "delete-from-cursor" (word-ends, -1) };
bind "<ctrl>k" { "delete-from-cursor" (paragraph-ends, 1) };
bind "<alt>space" { "delete-from-cursor" (whitespace, 1)
"insert-at-cursor" (" ") };
bind "<alt>KP_Space" { "delete-from-cursor" (whitespace, 1)
"insert-at-cursor" (" ") };
/*
* Some non-Emacs keybindings people are attached to
*/
bind "<ctrl>u" { "move-cursor" (paragraph-ends, -1, 0)
"delete-from-cursor" (paragraph-ends, 1) };
bind "<ctrl>h" { "delete-from-cursor" (chars, -1) };
bind "<ctrl>w" { "delete-from-cursor" (word-ends, -1) };
}
/*
* Bindings for GtkTextView
*/
@binding-set gtk-emacs-text-view
{
bind "<ctrl>p" { "move-cursor" (display-lines, -1, 0) };
bind "<shift><ctrl>p" { "move-cursor" (display-lines, -1, 1) };
bind "<ctrl>n" { "move-cursor" (display-lines, 1, 0) };
bind "<shift><ctrl>n" { "move-cursor" (display-lines, 1, 1) };
bind "<ctrl>space" { "set-anchor" () };
bind "<ctrl>KP_Space" { "set-anchor" () };
}
/*
* Bindings for GtkTreeView
*/
@binding-set gtk-emacs-tree-view
{
bind "<ctrl>s" { "start-interactive-search" () };
bind "<ctrl>f" { "move-cursor" (logical-positions, 1) };
bind "<ctrl>b" { "move-cursor" (logical-positions, -1) };
}
/*
* Bindings for menus
*/
@binding-set gtk-emacs-menu
{
bind "<ctrl>n" { "move-current" (next) };
bind "<ctrl>p" { "move-current" (prev) };
bind "<ctrl>f" { "move-current" (child) };
bind "<ctrl>b" { "move-current" (parent) };
}
entry {
-gtk-key-bindings: gtk-emacs-text-entry;
}
textview {
-gtk-key-bindings: gtk-emacs-text-entry, gtk-emacs-text-view;
}
treeview {
-gtk-key-bindings: gtk-emacs-tree-view;
}
GtkMenuShell {
-gtk-key-bindings: gtk-emacs-menu;
}
@import 'colors.css';

View File

@@ -0,0 +1,11 @@
general {
lock_cmd = pidof hyprlock || hyprlock
before_sleep_cmd = loginctl lock-session
after_sleep_cmd = hyprctl dispatch dpms on
}
listener {
timeout = 900
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on
}

View File

@@ -17,7 +17,7 @@ monitor=,preferred,auto,1
# =============================================================================
# PROGRAMS
# =============================================================================
$terminal = alacritty
$terminal = ghostty --gtk-single-instance=false
$fileManager = dolphin
$menu = rofi -show drun -show-icons
$runMenu = rofi -show run
@@ -27,6 +27,8 @@ $runMenu = rofi -show run
# =============================================================================
env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct
# Used by ~/.config/hypr/scripts/* to keep workspace IDs bounded.
env = HYPR_MAX_WORKSPACE,9
# =============================================================================
# INPUT CONFIGURATION
@@ -58,7 +60,7 @@ cursor {
general {
gaps_in = 5
gaps_out = 10
border_size = 2
border_size = 0
col.active_border = rgba(edb443ee) rgba(33ccffee) 45deg
col.inactive_border = rgba(595959aa)
@@ -91,15 +93,23 @@ decoration {
animations {
enabled = yes
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
# Hyprland supports bezier curves, not true spring physics.
# Use a mild overshoot plus GNOME-like window animation style.
bezier = overshoot, 0.05, 0.9, 0.1, 1.1
bezier = smoothOut, 0.36, 1, 0.3, 1
bezier = smoothInOut, 0.42, 0, 0.58, 1
bezier = linear, 0, 0, 1, 1
animation = windows, 1, 4, myBezier
animation = windowsOut, 1, 4, default, popin 80%
animation = border, 1, 10, default
animation = borderangle, 1, 8, default
animation = fade, 1, 4, default
animation = workspaces, 1, 4, default
# SPEED is in deciseconds (e.g. 6 == 600ms).
animation = windows, 1, 6, overshoot, gnomed
animation = windowsIn, 1, 6, overshoot, gnomed
animation = windowsOut, 1, 5, smoothInOut, gnomed
animation = windowsMove, 1, 6, smoothOut
animation = border, 0
animation = borderangle, 0
animation = fade, 1, 5, smoothOut
animation = workspaces, 1, 6, smoothOut, slidefade 15%
animation = specialWorkspace, 1, 6, smoothOut, slidevert
}
# =============================================================================
@@ -172,6 +182,8 @@ plugin {
workspace_method = first 1
# Only show workspaces with windows
skip_empty = true
# Show numeric workspace labels in the expo grid
show_workspace_numbers = true
# 3 columns -> 3x3 grid when 9 workspaces are visible
columns = 3
}
@@ -198,53 +210,27 @@ binds {
# WINDOW RULES
# =============================================================================
# Float dialogs
windowrulev2 = float, class:^()$,title:^()$
windowrulev2 = float, title:^(Picture-in-Picture)$
windowrulev2 = float, title:^(Open File)$
windowrulev2 = float, title:^(Save File)$
windowrulev2 = float, title:^(Confirm)$
windowrule = match:class ^()$, match:title ^()$, float on
windowrule = match:title ^(Picture-in-Picture)$, float on
windowrule = match:title ^(Open File)$, float on
windowrule = match:title ^(Save File)$, float on
windowrule = match:title ^(Confirm)$, float on
# Scratchpad windows - float and size
windowrulev2 = workspace special:htop silent, class:^(htop-scratch)$
windowrulev2 = float, class:^(htop-scratch)$
windowrulev2 = size 90% 90%, class:^(htop-scratch)$
windowrulev2 = center, class:^(htop-scratch)$
# Rumno OSD/notifications: treat as an overlay, not a "real" managed window.
# (Matches both class and title because rumno may set either depending on backend.)
windowrule = match:class ^(.*[Rr]umno.*)$, float on
windowrule = match:class ^(.*[Rr]umno.*)$, pin on
windowrule = match:class ^(.*[Rr]umno.*)$, center on
windowrule = match:class ^(.*[Rr]umno.*)$, decorate off
windowrule = match:class ^(.*[Rr]umno.*)$, no_shadow on
windowrule = match:title ^(.*[Rr]umno.*)$, float on
windowrule = match:title ^(.*[Rr]umno.*)$, pin on
windowrule = match:title ^(.*[Rr]umno.*)$, center on
windowrule = match:title ^(.*[Rr]umno.*)$, decorate off
windowrule = match:title ^(.*[Rr]umno.*)$, no_shadow on
windowrulev2 = workspace special:volume silent, class:^(?i)(pavucontrol|pavucontrol-qt|org\.pulseaudio\.pavucontrol)$
windowrulev2 = float, class:^(?i)(pavucontrol|pavucontrol-qt|org\.pulseaudio\.pavucontrol)$
windowrulev2 = size 90% 90%, class:^(?i)(pavucontrol|pavucontrol-qt|org\.pulseaudio\.pavucontrol)$
windowrulev2 = center, class:^(?i)(pavucontrol|pavucontrol-qt|org\.pulseaudio\.pavucontrol)$
windowrulev2 = workspace special:spotify silent, class:^(spotify)$
windowrulev2 = float, class:^(spotify)$
windowrulev2 = size 90% 90%, class:^(spotify)$
windowrulev2 = center, class:^(spotify)$
windowrulev2 = workspace special:element silent, class:^(Element)$
windowrulev2 = float, class:^(Element)$
windowrulev2 = size 90% 90%, class:^(Element)$
windowrulev2 = center, class:^(Element)$
windowrulev2 = workspace special:slack silent, class:^(Slack)$
windowrulev2 = float, class:^(Slack)$
windowrulev2 = size 90% 90%, class:^(Slack)$
windowrulev2 = center, class:^(Slack)$
windowrulev2 = workspace special:transmission silent, class:^(transmission-gtk)$
windowrulev2 = float, class:^(transmission-gtk)$
windowrulev2 = size 90% 90%, class:^(transmission-gtk)$
windowrulev2 = center, class:^(transmission-gtk)$
# Gmail and Messages (Chrome windows)
windowrulev2 = workspace special:gmail silent, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = float, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = size 90% 90%, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = center, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = workspace special:messages silent, class:^(google-chrome)$,title:^(Messages.*)$
windowrulev2 = float, class:^(google-chrome)$,title:^(Messages.*)$
windowrulev2 = size 90% 90%, class:^(google-chrome)$,title:^(Messages.*)$
windowrulev2 = center, class:^(google-chrome)$,title:^(Messages.*)$
# Scratchpad sizing handled by hyprscratch exec rules (see hyprland.nix)
# Using hyprscratch rules instead of windowrule to avoid affecting child windows (e.g. Slack meets)
# =============================================================================
# KEY BINDINGS
@@ -266,6 +252,7 @@ bind = $mainMod SHIFT, Return, exec, $terminal
# Overview (Hyprexpo)
# -----------------------------------------------------------------------------
bind = $mainMod, TAB, hyprexpo:expo, toggle
bind = $mainMod SHIFT, TAB, hyprexpo:expo, bring
bind = $mainMod, Q, killactive,
bind = $mainMod SHIFT, C, killactive,
bind = $mainMod SHIFT, Q, exit,
@@ -277,27 +264,17 @@ bind = $mainMod, V, exec, wl-paste | xdotool type --file -
bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome google-chrome-stable
# -----------------------------------------------------------------------------
# SCRATCHPADS (Special Workspaces)
# SCRATCHPADS (managed by hyprscratch daemon with auto-dismiss)
# -----------------------------------------------------------------------------
# Toggle scratchpads
bind = $modAlt, E, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh element '^Element$' - element-desktop
bind = $modAlt, G, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh gmail '^google-chrome$' '.*@gmail.com.*Gmail.*' google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox
bind = $modAlt, H, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh htop '^htop-scratch$' - alacritty --class htop-scratch --title htop -e htop
bind = $modAlt, M, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh messages '^google-chrome$' '^Messages.*' google-chrome-stable --new-window https://messages.google.com/web/conversations
bind = $modAlt, K, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh slack '^Slack$' - slack
bind = $modAlt, S, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh spotify '^spotify$' - spotify
bind = $modAlt, T, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh transmission '^transmission-gtk$' - transmission-gtk
bind = $modAlt, V, exec, ~/.config/hypr/scripts/toggle-scratchpad.sh volume '^(pavucontrol|pavucontrol-qt|org\.pulseaudio\.pavucontrol)$' - pavucontrol
# Move windows to scratchpads
bind = $modAlt SHIFT, E, movetoworkspace, special:element
bind = $modAlt SHIFT, G, movetoworkspace, special:gmail
bind = $modAlt SHIFT, H, movetoworkspace, special:htop
bind = $modAlt SHIFT, M, movetoworkspace, special:messages
bind = $modAlt SHIFT, K, movetoworkspace, special:slack
bind = $modAlt SHIFT, S, movetoworkspace, special:spotify
bind = $modAlt SHIFT, T, movetoworkspace, special:transmission
bind = $modAlt SHIFT, V, movetoworkspace, special:volume
bind = $modAlt, E, exec, hyprscratch toggle element
bind = $modAlt, G, exec, hyprscratch toggle gmail
bind = $modAlt, H, exec, hyprscratch toggle htop
bind = $modAlt, M, exec, hyprscratch toggle messages
bind = $modAlt, K, exec, hyprscratch toggle slack
bind = $modAlt, S, exec, hyprscratch toggle spotify
bind = $modAlt, T, exec, hyprscratch toggle transmission
bind = $modAlt, V, exec, hyprscratch toggle volume
bind = $modAlt, grave, exec, hyprscratch toggle dropdown
# Hidden workspace (like XMonad's NSP)
bind = $mainMod, X, movetoworkspace, special:NSP
@@ -315,10 +292,10 @@ bind = $mainMod, A, hy3:movefocus, l
bind = $mainMod, D, hy3:movefocus, r
# Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping
bind = $mainMod SHIFT, W, hy3:movewindow, u, once
bind = $mainMod SHIFT, S, hy3:movewindow, d, once
bind = $mainMod SHIFT, A, hy3:movewindow, l, once
bind = $mainMod SHIFT, D, hy3:movewindow, r, once
bind = $mainMod SHIFT, W, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh u once
bind = $mainMod SHIFT, S, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh d once
bind = $mainMod SHIFT, A, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh l once
bind = $mainMod SHIFT, D, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh r once
# Resize windows (Mod + Ctrl + WASD)
binde = $mainMod CTRL, W, resizeactive, 0 -50
@@ -338,12 +315,12 @@ bind = $hyper SHIFT, S, movewindow, mon:d
bind = $hyper SHIFT, A, movewindow, mon:l
bind = $hyper SHIFT, D, movewindow, mon:r
# Shift to empty workspace on screen direction (Hyper + Ctrl + WASD)
# Shift to empty workspace on screen direction (Super + Ctrl + Shift + WASD)
# Like XMonad's shiftToEmptyOnScreen
bind = $hyper CTRL, W, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh u
bind = $hyper CTRL, S, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh d
bind = $hyper CTRL, A, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh l
bind = $hyper CTRL, D, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh r
bind = $mainMod CTRL SHIFT, W, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh u
bind = $mainMod CTRL SHIFT, S, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh d
bind = $mainMod CTRL SHIFT, A, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh l
bind = $mainMod CTRL SHIFT, D, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh r
# -----------------------------------------------------------------------------
# LAYOUT CONTROL (XMonad-like with hy3)
@@ -394,28 +371,47 @@ bind = $mainMod SHIFT, equal, hy3:equalize, workspace
bind = $mainMod, N, hy3:killactive
# hy3:setswallow - set a window to swallow newly spawned windows
bind = $mainMod, M, hy3:setswallow, toggle
bind = $mainMod CTRL, M, hy3:setswallow, toggle
# Minimize to special workspace (like XMonad's minimizeWindow)
bind = $mainMod SHIFT, M, movetoworkspace, special:minimized
# Restore last minimized
bind = $modAlt, Return, togglespecialworkspace, minimized
# Minimize/unminimize (via special workspace)
bind = $mainMod, M, exec, ~/.config/hypr/scripts/minimize-active.sh minimized
bind = $mainMod SHIFT, M, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized
# Minimized "picker" mode:
# Open the minimized special workspace, focus a window, press Enter to restore it.
bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-mode.sh minimized
submap = minimized
bind = , Return, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized; hyprctl dispatch submap reset
bind = , Escape, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized
bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized
# Optional: basic focus navigation inside the picker.
bind = , H, movefocus, l
bind = , J, movefocus, d
bind = , K, movefocus, u
bind = , L, movefocus, r
bind = , left, movefocus, l
bind = , down, movefocus, d
bind = , up, movefocus, u
bind = , right, movefocus, r
submap = reset
# -----------------------------------------------------------------------------
# WORKSPACE CONTROL
# -----------------------------------------------------------------------------
# Switch workspaces (1-9, 0=10)
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Switch workspaces (1-9 only) on the currently focused monitor.
bind = $mainMod, 1, focusworkspaceoncurrentmonitor, 1
bind = $mainMod, 2, focusworkspaceoncurrentmonitor, 2
bind = $mainMod, 3, focusworkspaceoncurrentmonitor, 3
bind = $mainMod, 4, focusworkspaceoncurrentmonitor, 4
bind = $mainMod, 5, focusworkspaceoncurrentmonitor, 5
bind = $mainMod, 6, focusworkspaceoncurrentmonitor, 6
bind = $mainMod, 7, focusworkspaceoncurrentmonitor, 7
bind = $mainMod, 8, focusworkspaceoncurrentmonitor, 8
bind = $mainMod, 9, focusworkspaceoncurrentmonitor, 9
# Move window to workspace
bind = $mainMod SHIFT, 1, movetoworkspace, 1
@@ -427,52 +423,50 @@ bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Move and follow to workspace (like XMonad's shiftThenView)
bind = $mainMod CTRL, 1, movetoworkspacesilent, 1
bind = $mainMod CTRL, 1, workspace, 1
bind = $mainMod CTRL, 1, focusworkspaceoncurrentmonitor, 1
bind = $mainMod CTRL, 2, movetoworkspacesilent, 2
bind = $mainMod CTRL, 2, workspace, 2
bind = $mainMod CTRL, 2, focusworkspaceoncurrentmonitor, 2
bind = $mainMod CTRL, 3, movetoworkspacesilent, 3
bind = $mainMod CTRL, 3, workspace, 3
bind = $mainMod CTRL, 3, focusworkspaceoncurrentmonitor, 3
bind = $mainMod CTRL, 4, movetoworkspacesilent, 4
bind = $mainMod CTRL, 4, workspace, 4
bind = $mainMod CTRL, 4, focusworkspaceoncurrentmonitor, 4
bind = $mainMod CTRL, 5, movetoworkspacesilent, 5
bind = $mainMod CTRL, 5, workspace, 5
bind = $mainMod CTRL, 5, focusworkspaceoncurrentmonitor, 5
bind = $mainMod CTRL, 6, movetoworkspacesilent, 6
bind = $mainMod CTRL, 6, workspace, 6
bind = $mainMod CTRL, 6, focusworkspaceoncurrentmonitor, 6
bind = $mainMod CTRL, 7, movetoworkspacesilent, 7
bind = $mainMod CTRL, 7, workspace, 7
bind = $mainMod CTRL, 7, focusworkspaceoncurrentmonitor, 7
bind = $mainMod CTRL, 8, movetoworkspacesilent, 8
bind = $mainMod CTRL, 8, workspace, 8
bind = $mainMod CTRL, 8, focusworkspaceoncurrentmonitor, 8
bind = $mainMod CTRL, 9, movetoworkspacesilent, 9
bind = $mainMod CTRL, 9, workspace, 9
bind = $mainMod CTRL, 0, movetoworkspacesilent, 10
bind = $mainMod CTRL, 0, workspace, 10
bind = $mainMod CTRL, 9, focusworkspaceoncurrentmonitor, 9
# Workspace cycling (like XMonad's cycleWorkspaceOnCurrentScreen)
bind = $mainMod, backslash, exec, ~/.config/hypr/scripts/workspace-back.sh
# Toggle to the previous workspace on the current monitor using Hyprland's
# built-in per-monitor workspace history.
bind = $mainMod, backslash, workspace, previous_per_monitor
# Swap current workspace with another (like XMonad's swapWithCurrent)
bind = $hyper, 5, exec, ~/.config/hypr/scripts/swap-workspaces.sh
# Go to next empty workspace (like XMonad's moveTo Next emptyWS)
bind = $hyper, E, workspace, empty
bind = $hyper, E, exec, ~/.config/hypr/scripts/workspace-goto-empty.sh
# Move to next screen (like XMonad's shiftToNextScreenX)
bind = $mainMod, Z, focusmonitor, +1
bind = $mainMod SHIFT, Z, movewindow, mon:+1
# Shift to empty workspace and view (like XMonad's shiftToEmptyAndView)
bind = $mainMod SHIFT, H, movetoworkspace, empty
bind = $mainMod SHIFT, H, exec, ~/.config/hypr/scripts/workspace-move-to-empty.sh
# -----------------------------------------------------------------------------
# WINDOW MANAGEMENT
# -----------------------------------------------------------------------------
# Go to window (rofi window switcher)
bind = $mainMod, G, exec, rofi -show window -show-icons
# Go to window (rofi window switcher with icons)
bind = $mainMod, G, exec, ~/.config/hypr/scripts/go-to-window.sh
# Bring window (move to current workspace)
bind = $mainMod, B, exec, ~/.config/hypr/scripts/bring-window.sh
@@ -528,9 +522,13 @@ bind = $hyper SHIFT, L, exec, hyprlock
bind = $hyper, K, exec, rofi_kill_process.sh
bind = $hyper SHIFT, K, exec, rofi_kill_all.sh
bind = $hyper, R, exec, rofi-systemd
bind = $hyper, slash, exec, toggle_taffybar
bind = $hyper, 9, exec, start_synergy.sh
bind = $hyper, I, exec, rofi_select_input.hs
bind = $hyper, backslash, exec, /home/imalison/dotfiles/dotfiles/lib/functions/mpg341cx_input toggle
bind = $hyper, O, exec, rofi_paswitch
bind = $hyper, comma, exec, rofi_wallpaper.sh
bind = $hyper, Y, exec, rofi_agentic_skill
# Reload config
bind = $mainMod, R, exec, hyprctl reload
@@ -543,31 +541,22 @@ bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Scroll through workspaces
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
bind = $mainMod, mouse_down, exec, ~/.config/hypr/scripts/workspace-scroll.sh +1
bind = $mainMod, mouse_up, exec, ~/.config/hypr/scripts/workspace-scroll.sh -1
# =============================================================================
# AUTOSTART
# =============================================================================
# Start Hyprland session target for systemd user services (e.g., waybar)
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE
exec-once = systemctl --user start hyprland-session.target
exec-once = ~/.config/hypr/scripts/workspace-history.sh
# Scratchpad applications (spawn on demand via keybinds)
# exec-once = [workspace special:element silent] element-desktop
# exec-once = [workspace special:gmail silent] google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox
# exec-once = [workspace special:htop silent] alacritty --class htop-scratch --title htop -e htop
# exec-once = [workspace special:messages silent] google-chrome-stable --new-window https://messages.google.com/web/conversations
# exec-once = [workspace special:slack silent] slack
# exec-once = [workspace special:spotify silent] spotify
# exec-once = [workspace special:transmission silent] transmission-gtk
# exec-once = [workspace special:volume silent] pavucontrol
# Wire Hyprland into Home Manager's standard user-session targets.
# `graphical-session.target` pulls in most tray/SNI applets (which in turn pull in `tray.target`).
# Keep the systemd user manager in sync with the current Hyprland session before
# starting any session-bound units. Separate `exec-once` commands race.
exec-once = sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'
# Force a fresh daemon after compositor restarts so hyprscratch doesn't keep a stale socket.
exec-once = systemctl --user restart hyprscratch.service
exec-once = hypridle
# Clipboard history daemon
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
# Night light (optional - comment out if not needed)
# exec-once = wlsunset -l 37.7 -L -122.4

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
background {
monitor =
path = screenshot
blur_passes = 3
blur_size = 8
noise = 0.0117
contrast = 0.8916
brightness = 0.8172
vibrancy = 0.1696
}
input-field {
monitor =
size = 280, 56
outline_thickness = 3
dots_size = 0.2
dots_spacing = 0.2
outer_color = rgb(edb443)
inner_color = rgb(1e1e2e)
font_color = rgb(cdd6f4)
fade_on_empty = false
rounding = 12
placeholder_text = <i>Password...</i>
hide_input = false
position = 0, -80
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] echo "$(date +'%a %b %-d %I:%M %p')"
color = rgb(cdd6f4)
font_size = 40
font_family = Noto Sans
position = 0, 80
halign = center
valign = center
}

View File

View File

@@ -1,30 +1,40 @@
#!/usr/bin/env bash
# Bring window to current workspace (like XMonad's bringWindow)
# Uses rofi to select a window and moves it to the current workspace
# Uses rofi with icons to select a window, then moves it here.
set -euo pipefail
# Get current workspace
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/window-icon-map.sh"
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
# Get all windows and format for rofi
WINDOWS=$(hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0 and .workspace.id != '"$CURRENT_WS"') | "\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)"')
# Get windows on OTHER workspaces as TSV
WINDOW_DATA=$(hyprctl clients -j | jq -r --argjson cws "$CURRENT_WS" '
.[] | select(.workspace.id >= 0 and .workspace.id != $cws)
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
| @tsv')
if [ -z "$WINDOWS" ]; then
if [ -z "$WINDOW_DATA" ]; then
notify-send "Bring Window" "No windows on other workspaces"
exit 0
fi
# Show rofi menu
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Bring window" -format 's')
addresses=()
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
if [ -n "$SELECTION" ]; then
# Extract the window address (after the last |)
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
while IFS=$'\t' read -r address class title ws_id; do
icon=$(icon_for_class "$class")
addresses+=("$address")
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
"$class" "$title" "$ws_id" "$icon"
done <<< "$WINDOW_DATA" > "$TMPFILE"
# Move window to current workspace
INDEX=$(rofi -dmenu -i -show-icons -p "Bring window" -format i < "$TMPFILE") || exit 0
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
ADDRESS="${addresses[$INDEX]}"
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDRESS"
# Focus the window
hyprctl dispatch focuswindow "address:$ADDRESS"
fi

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail
# Print an "empty" workspace id within 1..$HYPR_MAX_WORKSPACE (default 9).
#
# Preference order (lowest id wins within each tier):
# 1. Workspace exists on the target monitor and has 0 windows
# 2. Workspace id does not exist at all (will be created on dispatch)
# 3. Workspace exists (elsewhere) and has 0 windows
#
# Usage:
# find-empty-workspace.sh [monitor] [exclude_id]
max_ws="${HYPR_MAX_WORKSPACE:-9}"
monitor="${1:-}"
exclude_id="${2:-}"
if [[ -z "${monitor}" ]]; then
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
fi
if [[ -z "${monitor}" || "${monitor}" == "null" ]]; then
exit 1
fi
workspaces_json="$(hyprctl workspaces -j 2>/dev/null || echo '[]')"
unused_candidate=""
elsewhere_empty_candidate=""
for i in $(seq 1 "${max_ws}"); do
if [[ -n "${exclude_id}" && "${i}" == "${exclude_id}" ]]; then
continue
fi
exists="$(jq -r --argjson id "${i}" '[.[] | select(.id == $id)] | length' <<<"${workspaces_json}")"
if [[ "${exists}" == "0" ]]; then
if [[ -z "${unused_candidate}" ]]; then
unused_candidate="${i}"
fi
continue
fi
windows="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .windows] | .[0]) // 0' <<<"${workspaces_json}")"
if [[ "${windows}" != "0" ]]; then
continue
fi
ws_monitor="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .monitor] | .[0]) // ""' <<<"${workspaces_json}")"
if [[ "${ws_monitor}" == "${monitor}" ]]; then
printf '%s\n' "${i}"
exit 0
fi
if [[ -z "${elsewhere_empty_candidate}" ]]; then
elsewhere_empty_candidate="${i}"
fi
done
if [[ -n "${unused_candidate}" ]]; then
printf '%s\n' "${unused_candidate}"
exit 0
fi
if [[ -n "${elsewhere_empty_candidate}" ]]; then
printf '%s\n' "${elsewhere_empty_candidate}"
exit 0
fi
exit 1

View File

@@ -24,7 +24,7 @@ fi
COUNT=0
for ADDR in $WINDOWS; do
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDR"
((COUNT++))
COUNT=$((COUNT + 1))
done
notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'"

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Go to a window selected via rofi (with icons from desktop entries).
# Replaces "rofi -show window" which doesn't work well on Wayland.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/window-icon-map.sh"
# Get all windows on regular workspaces as TSV
WINDOW_DATA=$(hyprctl clients -j | jq -r '
.[] | select(.workspace.id >= 0)
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
| @tsv')
[ -n "$WINDOW_DATA" ] || exit 0
addresses=()
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
while IFS=$'\t' read -r address class title ws_id; do
icon=$(icon_for_class "$class")
addresses+=("$address")
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
"$class" "$title" "$ws_id" "$icon"
done <<< "$WINDOW_DATA" > "$TMPFILE"
INDEX=$(rofi -dmenu -i -show-icons -p "Go to window" -format i < "$TMPFILE") || exit 0
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
hyprctl dispatch focuswindow "address:${addresses[$INDEX]}"
fi

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Minimize the active window by moving it to a special workspace without
# toggling that special workspace open.
#
# Usage: minimize-active.sh <name>
# Example: minimize-active.sh minimized
set -euo pipefail
NAME="${1:-minimized}"
NAME="${NAME#special:}"
if ! command -v hyprctl >/dev/null 2>&1; then
exit 0
fi
if ! command -v jq >/dev/null 2>&1; then
# We could parse plain output, but jq should exist in this setup; if it
# doesn't, fail soft.
exit 0
fi
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
exit 0
fi
# If the minimized special workspace is currently visible, closing it after the
# move keeps the window hidden (what "minimize" usually means).
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
SPECIAL_OPEN="$(
hyprctl -j monitors 2>/dev/null \
| jq -r --arg n "special:$NAME" --argjson mid "${MONITOR_ID:-0}" '
.[]
| select(.id == $mid)
| (.specialWorkspace.name // "")
| select(. == $n)
' \
| head -n 1 \
|| true
)"
hyprctl dispatch movetoworkspacesilent "special:${NAME},address:${ADDR}" >/dev/null 2>&1 || true
if [ -n "$SPECIAL_OPEN" ]; then
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
fi
exit 0

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Exit minimized picker mode:
# - Hide the minimized special workspace on the active monitor (if visible)
# - Reset the submap
#
# Usage: minimized-cancel.sh <name>
set -euo pipefail
NAME="${1:-minimized}"
NAME="${NAME#special:}"
SPECIAL_WS="special:${NAME}"
if ! command -v hyprctl >/dev/null 2>&1; then
exit 0
fi
if ! command -v jq >/dev/null 2>&1; then
exit 0
fi
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
MONITOR_ID=0
fi
OPEN="$(
hyprctl -j monitors 2>/dev/null \
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
| head -n 1 \
|| true
)"
if [ "$OPEN" = "$SPECIAL_WS" ]; then
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
fi
hyprctl dispatch submap reset >/dev/null 2>&1 || true
exit 0

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Enter a "picker" mode for minimized windows:
# - Ensure the minimized special workspace is visible on the active monitor
# - Switch Hyprland into a submap so Enter restores and Escape cancels
#
# Usage: minimized-mode.sh <name>
set -euo pipefail
NAME="${1:-minimized}"
NAME="${NAME#special:}"
SPECIAL_WS="special:${NAME}"
if ! command -v hyprctl >/dev/null 2>&1; then
exit 0
fi
if ! command -v jq >/dev/null 2>&1; then
exit 0
fi
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
MONITOR_ID=0
fi
OPEN="$(
hyprctl -j monitors 2>/dev/null \
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
| head -n 1 \
|| true
)"
# Ensure it's visible (but don't toggle it off if already open).
if [ "$OPEN" != "$SPECIAL_WS" ]; then
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
fi
hyprctl dispatch submap minimized >/dev/null 2>&1 || true
exit 0

View File

@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Move the active window in a direction and warp the cursor to keep its
# relative position inside the moved window.
set -euo pipefail
export PATH="/run/current-system/sw/bin:${PATH}"
if [[ $# -lt 1 ]]; then
echo "usage: $0 <dir> [mode]" >&2
exit 1
fi
dir="$1"
mode="${2:-}"
if ! command -v hyprctl >/dev/null; then
exit 0
fi
move_window() {
if [[ -n "$mode" ]]; then
hyprctl dispatch hy3:movewindow "$dir, $mode" >/dev/null 2>&1 || true
else
hyprctl dispatch hy3:movewindow "$dir" >/dev/null 2>&1 || true
fi
}
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
cur_json="$(hyprctl -j cursorpos 2>/dev/null || true)"
if [[ -z "$win_json" || "$win_json" == "null" || -z "$cur_json" || "$cur_json" == "null" ]]; then
move_window
exit 0
fi
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
cur_x="$(jq -er '.x' <<<"$cur_json" 2>/dev/null || true)"
cur_y="$(jq -er '.y' <<<"$cur_json" 2>/dev/null || true)"
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ || ! "$cur_x" =~ ^-?[0-9]+$ || ! "$cur_y" =~ ^-?[0-9]+$ ]]; then
move_window
exit 0
fi
rel_x=$((cur_x - win_x))
rel_y=$((cur_y - win_y))
move_window
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
if [[ -z "$win_json" || "$win_json" == "null" ]]; then
exit 0
fi
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ ]]; then
exit 0
fi
if ((rel_x < 0)); then
rel_x=0
elif ((rel_x > win_w)); then
rel_x=$win_w
fi
if ((rel_y < 0)); then
rel_y=0
elif ((rel_y > win_h)); then
rel_y=$win_h
fi
new_x=$((win_x + rel_x))
new_y=$((win_y + rel_y))
hyprctl dispatch movecursor "$new_x" "$new_y" >/dev/null 2>&1 || true

View File

@@ -4,8 +4,9 @@
set -euo pipefail
# Get current workspace and focused window
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/window-icon-map.sh"
FOCUSED=$(hyprctl activewindow -j | jq -r '.address')
if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
@@ -13,21 +14,30 @@ if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
exit 0
fi
# Get all windows except focused
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.workspace.id >= 0 and .address != \"$FOCUSED\") | \"\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)\"")
# Get all windows except focused as TSV
WINDOW_DATA=$(hyprctl clients -j | jq -r --arg focused "$FOCUSED" '
.[] | select(.workspace.id >= 0 and .address != $focused)
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
| @tsv')
if [ -z "$WINDOWS" ]; then
if [ -z "$WINDOW_DATA" ]; then
notify-send "Replace Window" "No other windows available"
exit 0
fi
# Show rofi menu
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Replace with" -format 's')
addresses=()
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
if [ -n "$SELECTION" ]; then
# Extract the window address
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
while IFS=$'\t' read -r address class title ws_id; do
icon=$(icon_for_class "$class")
addresses+=("$address")
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
"$class" "$title" "$ws_id" "$icon"
done <<< "$WINDOW_DATA" > "$TMPFILE"
# Swap windows using hy3
hyprctl dispatch hy3:movewindow "address:$ADDRESS"
INDEX=$(rofi -dmenu -i -show-icons -p "Replace with" -format i < "$TMPFILE") || exit 0
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
hyprctl dispatch hy3:movewindow "address:${addresses[$INDEX]}"
fi

View File

@@ -6,6 +6,7 @@
set -euo pipefail
DIRECTION="$1"
max_ws="${HYPR_MAX_WORKSPACE:-9}"
# Track the current monitor so we can return
ORIG_MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
@@ -21,14 +22,17 @@ if [ "$MONITOR" = "$ORIG_MONITOR" ]; then
exit 0
fi
# Find an empty workspace or create one
# First check if there's an empty workspace on this monitor
EMPTY_WS=$(hyprctl workspaces -j | jq -r ".[] | select(.windows == 0 and .monitor == \"$MONITOR\") | .id" | head -1)
# Find an empty workspace within 1..$HYPR_MAX_WORKSPACE.
EMPTY_WS="$(~/.config/hypr/scripts/find-empty-workspace.sh "${MONITOR}" 2>/dev/null || true)"
if [[ -z "${EMPTY_WS}" ]]; then
# No empty workspace available within the cap; restore focus and bail.
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
exit 0
fi
if [ -z "$EMPTY_WS" ]; then
# No empty workspace, find next available workspace number
MAX_WS=$(hyprctl workspaces -j | jq -r 'map(.id) | max')
EMPTY_WS=$((MAX_WS + 1))
if (( EMPTY_WS < 1 || EMPTY_WS > max_ws )); then
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
exit 0
fi
# Ensure the workspace exists on the target monitor

View File

@@ -4,6 +4,8 @@
set -euo pipefail
max_ws="${HYPR_MAX_WORKSPACE:-9}"
CURRENT_WS="$(hyprctl activeworkspace -j | jq -r '.id')"
if [[ -z "${CURRENT_WS}" || "${CURRENT_WS}" == "null" ]]; then
exit 0
@@ -13,7 +15,7 @@ TARGET_WS="${1:-}"
if [[ -z "${TARGET_WS}" ]]; then
WS_LIST="$({
seq 1 10
seq 1 "${max_ws}"
hyprctl workspaces -j | jq -r '.[].id' 2>/dev/null || true
} | awk 'NF {print $1}' | awk '!seen[$0]++' | sort -n)"
@@ -33,6 +35,11 @@ if ! [[ "${TARGET_WS}" =~ ^-?[0-9]+$ ]]; then
exit 1
fi
if (( TARGET_WS < 1 || TARGET_WS > max_ws )); then
notify-send "Swap Workspace" "Workspace out of range (1-${max_ws}): ${TARGET_WS}"
exit 1
fi
WINDOWS_CURRENT="$(hyprctl clients -j | jq -r --arg ws "${CURRENT_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"
WINDOWS_TARGET="$(hyprctl clients -j | jq -r --arg ws "${TARGET_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"

View File

@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Restore a minimized window by moving it out of a special workspace.
#
# Usage: unminimize-last.sh <name>
# Example: unminimize-last.sh minimized
set -euo pipefail
NAME="${1:-minimized}"
NAME="${NAME#special:}"
SPECIAL_WS="special:${NAME}"
if ! command -v hyprctl >/dev/null 2>&1; then
exit 0
fi
if ! command -v jq >/dev/null 2>&1; then
exit 0
fi
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
ACTIVE_ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
ACTIVE_WS="$(printf '%s' "$ACTIVE_JSON" | jq -r '.workspace.name // empty')"
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
# Destination is the normal active workspace for the active monitor.
DEST_WS="$(
hyprctl -j monitors 2>/dev/null \
| jq -r --argjson mid "${MONITOR_ID:-0}" '.[] | select(.id == $mid) | .activeWorkspace.name' \
| head -n 1 \
|| true
)"
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
DEST_WS="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.name // empty' || true)"
fi
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
exit 0
fi
# If we're focused on a minimized window already, restore that one.
ADDR=""
if [ "$ACTIVE_WS" = "$SPECIAL_WS" ] && [ -n "$ACTIVE_ADDR" ] && [ "$ACTIVE_ADDR" != "null" ]; then
ADDR="$ACTIVE_ADDR"
else
# Otherwise, restore the "most recent" minimized window we can find.
# focusHistoryID tends to have 0 as most recent; pick the smallest value.
ADDR="$(
hyprctl -j clients 2>/dev/null \
| jq -r --arg sw "$SPECIAL_WS" '
[ .[]
| select(.workspace.name == $sw)
| { addr: .address, fh: (.focusHistoryID // 999999999) }
]
| sort_by(.fh)
| (.[0].addr // empty)
' \
| head -n 1 \
|| true
)"
fi
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
exit 0
fi
hyprctl dispatch movetoworkspacesilent "${DEST_WS},address:${ADDR}" >/dev/null 2>&1 || true
hyprctl dispatch focuswindow "address:${ADDR}" >/dev/null 2>&1 || true
# If the minimized special workspace is currently visible, close it so we don't
# leave things in a special state after a restore.
SPECIAL_OPEN="$(
hyprctl -j monitors 2>/dev/null \
| jq -r --arg n "$SPECIAL_WS" --argjson mid "${MONITOR_ID:-0}" '
.[]
| select(.id == $mid)
| (.specialWorkspace.name // "")
| select(. == $n)
' \
| head -n 1 \
|| true
)"
if [ -n "$SPECIAL_OPEN" ]; then
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
fi
exit 0

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Source this file to get icon_for_class function.
# Builds a mapping from window class → freedesktop icon name
# by scanning .desktop files for StartupWMClass and Icon fields.
#
# Usage:
# source "$(dirname "$0")/window-icon-map.sh"
# icon=$(icon_for_class "google-chrome")
declare -A _WINDOW_ICON_MAP
_build_window_icon_map() {
local IFS=':'
local -a search_dirs=()
local dir
for dir in ${XDG_DATA_DIRS:-/run/current-system/sw/share:/usr/share:/usr/local/share}; do
[ -d "$dir/applications" ] && search_dirs+=("$dir/applications")
done
[ -d "$HOME/.local/share/applications" ] && search_dirs+=("$HOME/.local/share/applications")
[ ${#search_dirs[@]} -eq 0 ] && return
# Expand globs per-directory so the pattern works correctly
local -a desktop_files=()
for dir in "${search_dirs[@]}"; do
desktop_files+=("$dir"/*.desktop)
done
[ ${#desktop_files[@]} -eq 0 ] && return
# Single grep pass across all desktop files
local -A file_icons file_wmclass
local filepath line
while IFS=: read -r filepath line; do
case "$line" in
Icon=*)
[ -z "${file_icons[$filepath]:-}" ] && file_icons["$filepath"]="${line#Icon=}"
;;
StartupWMClass=*)
[ -z "${file_wmclass[$filepath]:-}" ] && file_wmclass["$filepath"]="${line#StartupWMClass=}"
;;
esac
done < <(grep -H '^Icon=\|^StartupWMClass=' "${desktop_files[@]}" 2>/dev/null)
# Build class → icon map
local icon wm_class bn name
for filepath in "${!file_icons[@]}"; do
icon="${file_icons[$filepath]}"
[ -n "$icon" ] || continue
wm_class="${file_wmclass[$filepath]:-}"
if [ -n "$wm_class" ]; then
_WINDOW_ICON_MAP["${wm_class,,}"]="$icon"
fi
bn="${filepath##*/}"
name="${bn%.desktop}"
_WINDOW_ICON_MAP["${name,,}"]="$icon"
done
}
_build_window_icon_map
icon_for_class() {
local class_lower="${1,,}"
echo "${_WINDOW_ICON_MAP[$class_lower]:-$class_lower}"
}

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
state_dir="${runtime_dir}/hypr"
prev_file="${state_dir}/prev-workspace"
prev="$(cat "${prev_file}" 2>/dev/null || true)"
if [[ -z "${prev}" ]]; then
exit 0
fi
hyprctl dispatch workspace "${prev}" >/dev/null 2>&1 || true

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
ws="$(
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
)"
if [[ -z "${ws}" ]]; then
exit 0
fi
hyprctl dispatch workspace "${ws}" >/dev/null 2>&1 || true

View File

@@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
sig="${HYPRLAND_INSTANCE_SIGNATURE:-}"
if [[ -z "$sig" ]]; then
exit 0
fi
sock="${runtime_dir}/hypr/${sig}/.socket2.sock"
state_dir="${runtime_dir}/hypr"
last_file="${state_dir}/last-workspace"
prev_file="${state_dir}/prev-workspace"
mkdir -p "${state_dir}"
# Initialize current workspace to avoid empty state.
if command -v hyprctl >/dev/null 2>&1; then
cur_id="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
if [[ -n "${cur_id}" && "${cur_id}" != "null" ]]; then
echo "${cur_id}" > "${last_file}"
fi
fi
# Wait for the event socket to be ready.
while [[ ! -S "${sock}" ]]; do
sleep 0.2
done
nc -U "${sock}" | while read -r line; do
case "${line}" in
workspace*">>"*)
payload="${line#*>>}"
# Handle workspacev2 payloads: id,name
if [[ "${payload}" == *","* ]]; then
ws_id="${payload%%,*}"
ws_name="${payload#*,}"
else
ws_id="${payload}"
ws_name="${payload}"
fi
# Ignore special/negative workspaces.
if [[ "${ws_id}" =~ ^- ]] || [[ "${ws_name}" == special:* ]]; then
continue
fi
ws_ident="${ws_name}"
if [[ -z "${ws_ident}" ]]; then
ws_ident="${ws_id}"
fi
prev="$(cat "${last_file}" 2>/dev/null || true)"
if [[ -n "${prev}" && "${ws_ident}" != "${prev}" ]]; then
echo "${prev}" > "${prev_file}"
fi
echo "${ws_ident}" > "${last_file}"
;;
esac
done

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
ws="$(
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
)"
if [[ -z "${ws}" ]]; then
exit 0
fi
hyprctl dispatch movetoworkspace "${ws}" >/dev/null 2>&1 || true

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
max_ws="${HYPR_MAX_WORKSPACE:-9}"
delta="${1:-}"
case "${delta}" in
+1|-1) ;;
next) delta="+1" ;;
prev) delta="-1" ;;
*)
exit 2
;;
esac
cur="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
if ! [[ "${cur}" =~ ^[0-9]+$ ]]; then
exit 0
fi
if (( cur < 1 )); then
cur=1
elif (( cur > max_ws )); then
cur="${max_ws}"
fi
if [[ "${delta}" == "+1" ]]; then
if (( cur >= max_ws )); then
nxt=1
else
nxt=$((cur + 1))
fi
else
if (( cur <= 1 )); then
nxt="${max_ws}"
else
nxt=$((cur - 1))
fi
fi
hyprctl dispatch workspace "${nxt}" >/dev/null 2>&1 || true

View File

View File

@@ -1,106 +0,0 @@
{
"global": {
"check_for_updates_on_startup": true,
"show_in_menu_bar": true,
"show_profile_name_in_menu_bar": false
},
"profiles": [
{
"complex_modifications": {
"parameters": {
"basic.to_if_alone_timeout_milliseconds": 1000
},
"rules": [
{
"manipulators": [
{
"description": "Change right command to command+control+option+shift.",
"from": {
"key_code": "right_command",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "left_shift",
"modifiers": [
"left_command",
"left_control",
"left_option"
]
}
],
"to_if_alone": [
{
"key_code": "escape",
"modifiers": {
"optional": [
"any"
]
}
}
],
"type": "basic"
}
]
}
]
},
"devices": [
{
"disable_built_in_keyboard_if_exists": false,
"fn_function_keys": {},
"identifiers": {
"is_keyboard": true,
"is_pointing_device": false,
"product_id": 610,
"vendor_id": 1452
},
"ignore": false,
"simple_modifications": {}
},
{
"disable_built_in_keyboard_if_exists": false,
"fn_function_keys": {},
"identifiers": {
"is_keyboard": true,
"is_pointing_device": false,
"product_id": 597,
"vendor_id": 1452
},
"ignore": false,
"simple_modifications": {}
}
],
"fn_function_keys": {
"f1": "vk_consumer_brightness_down",
"f10": "mute",
"f11": "volume_down",
"f12": "volume_up",
"f2": "vk_consumer_brightness_up",
"f3": "vk_mission_control",
"f4": "vk_launchpad",
"f5": "vk_consumer_illumination_down",
"f6": "vk_consumer_illumination_up",
"f7": "vk_consumer_previous",
"f8": "vk_consumer_play",
"f9": "vk_consumer_next"
},
"name": "Default profile",
"one_to_many_mappings": {},
"selected": true,
"simple_modifications": {
"caps_lock": "left_control"
},
"standalone_keys": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi",
"standalone_keys_delay_milliseconds": 200
}
}
]
}

View File

@@ -1,21 +0,0 @@
[Added Associations]
video/x-matroska=vlc.desktop;
audio/flac=vlc.desktop;
image/jpeg=feh.desktop;
video/x-msvideo=vlc.desktop;
text/vnd.trolltech.linguist=vlc.desktop;
audio/mpeg=vlc.desktop;
application/pdf=okularApplication_pdf.desktop;
image/png=okularApplication_kimgio.desktop;
video/mp4=vlc.desktop;org.gnome.Totem.desktop;
x-scheme-handler/magnet=userapp-transmission-gtk-24GQLZ.desktop;
element=element-desktop.desktop
[Default Applications]
text/html=google-chrome.desktop
x-scheme-handler/http=google-chrome.desktop
x-scheme-handler/https=google-chrome.desktop
x-scheme-handler/about=google-chrome.desktop
x-scheme-handler/unknown=google-chrome.desktop
x-scheme-handler/magnet=userapp-transmission-gtk-24GQLZ.desktop
x-scheme-handler/element=element-desktop.desktop

View File

@@ -1,128 +0,0 @@
[remmina_pref]
secret=SEkwV+ilNl+x9eTDKU6tLKFTKdJv2OK2ROlV3Z4K0uY=
uid=Linux+4.7.4-1-ARCH+x86_64+en_US+52817413
bdate=736234
save_view_mode=true
save_when_connect=true
survey=false
invisible_toolbar=false
floating_toolbar_placement=0
toolbar_placement=3
always_show_tab=true
hide_connection_toolbar=false
default_action=0
scale_quality=3
ssh_loglevel=1
screenshot_path=/home/imalison/Pictures
ssh_parseconfig=true
hide_toolbar=false
hide_statusbar=false
small_toolbutton=false
view_file_mode=0
resolutions=640x480,800x600,1024x768,1152x864,1280x960,1400x1050
keystrokes=Send hello world§hello world\\n
main_width=668
main_height=1321
main_maximize=false
main_sort_column_id=1
main_sort_order=0
expanded_group=
toolbar_pin_down=false
sshtunnel_port=4732
applet_new_ontop=false
applet_hide_count=false
applet_enable_avahi=false
disable_tray_icon=false
dark_tray_icon=false
recent_maximum=10
default_mode=0
tab_mode=0
show_buttons_icons=0
show_menu_icons=0
auto_scroll_step=10
hostkey=65508
shortcutkey_fullscreen=102
shortcutkey_autofit=49
shortcutkey_nexttab=65363
shortcutkey_prevtab=65361
shortcutkey_scale=115
shortcutkey_grab=65508
shortcutkey_screenshot=65481
shortcutkey_minimize=65478
shortcutkey_disconnect=65473
shortcutkey_toolbar=116
vte_font=
vte_allow_bold_text=true
vte_lines=512
vte_system_colors=false
vte_foreground_color=rgb(192,192,192)
vte_background_color=rgb(0,0,0)
rdp_use_client_keymap=0
rdp_quality_0=6F
rdp_quality_1=7
rdp_quality_2=1
rdp_quality_9=80
datadir_path=
remmina_file_name=%G_%P_%N_%h
screenshot_name=remmina_%p_%h_%Y%m%d-%H%M%S
deny_screenshot_clipboard=true
confirm_close=true
use_primary_password=false
unlock_timeout=300
unlock_password=
lock_connect=false
lock_edit=false
lock_view_passwords=false
enc_mode=1
audit=false
trust_all=false
prevent_snap_welcome_message=false
last_quickconnect_protocol=
fullscreen_on_auto=true
always_show_notes=false
hide_searchbar=false
ssh_tcp_keepidle=20
ssh_tcp_keepintvl=10
ssh_tcp_keepcnt=3
ssh_tcp_usrtimeout=60000
dark_theme=false
fullscreen_toolbar_visibility=0
shortcutkey_multimon=65365
shortcutkey_viewonly=109
vte_shortcutkey_copy=99
vte_shortcutkey_paste=118
vte_shortcutkey_select_all=97
vte_shortcutkey_increase_font=65365
vte_shortcutkey_decrease_font=65366
vte_shortcutkey_search_text=103
grab_color=#00ff00
grab_color_switch=false
[ssh_colors]
background=#d5ccba
cursor=#45373c
cursor_foreground=#d5ccba
highlight=#45373c
highlight_foreground=#d5ccba
colorBD=#45373c
foreground=#45373c
color0=#20111b
color1=#be100e
color2=#858162
color3=#eaa549
color4=#426a79
color5=#97522c
color6=#989a9c
color7=#968c83
color8=#5e5252
color9=#be100e
color10=#858162
color11=#eaa549
color12=#426a79
color13=#97522c
color14=#989a9c
color15=#d5ccba
[remmina]
name=
ignore-tls-errors=1

View File

@@ -0,0 +1,53 @@
[remmina_pref]
secret=
uid=
bdate=
save_view_mode=true
save_when_connect=true
survey=false
invisible_toolbar=false
floating_toolbar_placement=0
toolbar_placement=3
always_show_tab=true
hide_connection_toolbar=false
default_action=0
scale_quality=3
ssh_loglevel=1
screenshot_path=
ssh_parseconfig=true
hide_toolbar=false
hide_statusbar=false
small_toolbutton=false
view_file_mode=0
resolutions=640x480,800x600,1024x768,1152x864,1280x960,1400x1050
main_width=0
main_height=0
main_maximize=false
main_sort_column_id=1
main_sort_order=0
expanded_group=
toolbar_pin_down=false
sshtunnel_port=4732
applet_new_ontop=false
applet_hide_count=false
applet_enable_avahi=false
disable_tray_icon=false
dark_tray_icon=false
recent_maximum=10
default_mode=0
tab_mode=0
show_buttons_icons=0
show_menu_icons=0
auto_scroll_step=10
confirm_close=true
use_primary_password=false
unlock_timeout=300
unlock_password=
lock_connect=false
lock_edit=false
lock_view_passwords=false
enc_mode=1
audit=false
trust_all=false
prevent_snap_welcome_message=false
last_quickconnect_protocol=

View File

@@ -11,7 +11,7 @@ configuration {
font: "Fira Code 10";
show-icons: true;
display-drun: "";
drun-display-format: "{name}";
drun-display-format: "{name} {generic}";
disable-history: false;
sidebar-mode: false;
}
@@ -25,7 +25,8 @@ window {
border: 0px;
border-color: @ac;
border-radius: 12px;
width: 40%;
width: 88%;
height: 78%;
location: center;
x-offset: 0;
y-offset: 0;
@@ -64,8 +65,8 @@ inputbar {
listview {
background-color: @al;
padding: 10px;
columns: 2;
lines: 7;
columns: 1;
lines: 18;
spacing: 1%;
cycle: false;
dynamic: true;

View File

@@ -4,9 +4,9 @@ first-run=false
[account]
email=IvanMalison@gmail.com
logged-in=true
logged-in=false
token=
token-secret=d3f4ef69df4242e9b10ca034b28f6831
token-secret=
[hotkeys]
captureFullScreen=Shift+Alt+1
@@ -31,10 +31,10 @@ proxy-req-auth=false
check-updates-automatically=true
[uploaders]
imgur\access-token=bcea050c708998597610f22a20c2121afa4cfad9
imgur\anonymous=false
imgur\copy-direct-link=false
imgur\copy-link=true
imgur\name-format=Screenshot at %H:%M:%S
imgur\refresh-token=9890577aa0486ecb5ddc5a04cf9ceb0228e91b26
imgur\username=
imgur\\access-token=
imgur\\anonymous=false
imgur\\copy-direct-link=false
imgur\\copy-link=true
imgur\\name-format=Screenshot at %H:%M:%S
imgur\\refresh-token=
imgur\\username=

View File

@@ -0,0 +1,29 @@
# Agentic Session Preferences (taffybar)
## Ad hoc running
- Prefer `just run` (foreground) and `just restart` (background) when running taffybar during iterative work.
- If running manually, prefer `direnv exec . cabal run` (or `nix develop -c cabal run` if direnv is not active) so you get the right build inputs and environment.
## Multiplexer session titling
- If the `TMUX` or `ZELLIJ` environment variable is set, treat this chat as the controller for the current tmux or zellij session.
- Use `set_multiplexer_title '<project> - <task>'` to update the title. The command detects tmux vs. zellij internally, prefers tmux when both are present, and no-ops outside a multiplexer.
- Maintain a session/window/pane title that updates when the task focus changes substantially.
- Prefer automatic titling: infer a concise <task> from the current user request and context without asking.
- Title format: "<project> - <task>".
- <project> is the basename of the current project directory.
- Prefer git repo root basename if available; otherwise use basename of the current working directory.
- <task> is a short, user-friendly description of what we are doing.
- Ask for a short descriptive <task> only when the task is ambiguous or you are not confident in an inferred title.
- When the task changes substantially, update the <task> automatically if clear; otherwise ask for an updated <task>.
- When a title is provided or updated, immediately run `set_multiplexer_title '<project> - <task>'`; do not call raw tmux or zellij rename commands unless debugging the helper itself.
## Pane usage
- Do not create extra panes or windows unless the user asks.
## NixOS workflow
- This system is managed with a Nix flake at `~/dotfiles/nixos`.
- Use `just switch` from that directory for rebuilds instead of plain `nixos-rebuild`.
- Host configs live under `machines/`; choose the appropriate host when needed.
## Skills
A skill is a set of local instructions to follow that is stored in a `SKILL.md` file.

View File

@@ -0,0 +1,60 @@
set shell := ["bash", "-euo", "pipefail", "-c"]
# Run taffybar in the foreground (blocks the terminal).
run:
@scripts/taffybar-run
# Restart taffybar in the background using cabal run.
restart:
@scripts/taffybar-restart
# Restart with no vendor CSS (taffybar's built-in stylesheet) loaded.
restart-no-vendor-css:
@TAFFYBAR_DISABLE_VENDOR_CSS=1 scripts/taffybar-restart
# Restart with no user CSS loaded (ignores ~/.config/taffybar/taffybar.css and cssPaths).
restart-no-user-css:
@TAFFYBAR_DISABLE_USER_CSS=1 scripts/taffybar-restart
# Restart with no taffybar CSS loaded at all (GTK theme only).
restart-empty-css:
@TAFFYBAR_DISABLE_VENDOR_CSS=1 TAFFYBAR_DISABLE_USER_CSS=1 scripts/taffybar-restart
# Restart with an explicit colon-separated list of CSS files.
# Example: just restart-css-paths 'menu-debug.css:taffybar.css'
restart-css-paths paths:
@TAFFYBAR_CSS_PATHS='{{paths}}' scripts/taffybar-restart
# Restart using only the minimal debug stylesheet in menu-debug.css.
restart-menu-debug:
@TAFFYBAR_CSS_PATHS='menu-debug.css' scripts/taffybar-restart
# Restart using `scratch.css` (initially empty) for bisecting CSS leakage into menus.
restart-scratch:
@TAFFYBAR_CSS_PATHS='scratch.css' scripts/taffybar-restart
# Capture the reserved top area (taffybar) on the focused monitor.
screenshot:
@scripts/taffybar-screenshot
# Capture the reserved top area on all monitors (one file per monitor).
screenshot-all:
@scripts/taffybar-screenshot-all
# Screenshot the entire focused monitor (useful when debugging popup menus).
screenshot-monitor:
@scripts/taffybar-screenshot-focused-monitor
# Restart taffybar, pop up an SNI menu automatically (and a submenu if present),
# then screenshot the focused monitor. Optional argument selects the SNI item
# by substring match.
capture-sni-menu match="":
@if [[ -n "{{match}}" ]]; then scripts/taffybar-capture-sni-menu "{{match}}"; else scripts/taffybar-capture-sni-menu; fi
# Trigger an SNI menu popup via DBus (taffybar.debug).
popup-sni-menu match="" submenu="1":
@scripts/taffybar-popup-sni-menu "{{match}}" "{{submenu}}"
# Crop the top bar out of an existing screenshot (defaults to 56px high).
crop in out="":
@if [[ -n "{{out}}" ]]; then scripts/taffybar-crop-bar "{{in}}" "{{out}}"; else scripts/taffybar-crop-bar "{{in}}"; fi

View File

@@ -0,0 +1,85 @@
# Taffybar SNI Menu CSS Debug Notes
This documents the root cause and debugging workflow for the "SNI tray submenu text is unreadable" and "menus become transparent" issues.
## Background
Taffybar renders StatusNotifierItem (SNI) menus using GTK menus created from the application's DBusMenu (`StatusNotifierItem.Menu`).
Key GTK behavior that matters here:
- SNI menus are shown as separate popup windows (GTK creates `window.popup` / menu surfaces).
- Those popup windows can *inherit style context* from the widget chain they are attached to (`Gtk.menuAttachToWidget`).
- This means bar CSS can "leak" into menu windows even though the menus are not children of the bar in the widget hierarchy.
## What We Observed
1. **Blanket descendant rules leak into menus**
- Rules like `.taffy-window .taffy-box * { color: ... }` can override the theme foreground color for menu text.
- Rules like `... * { background-color: transparent; }` can make menu surfaces transparent (especially submenus).
2. **The main culprit was the "pill solid" background reset**
- The bar used a broad "reset" to make widget pills look solid by clearing backgrounds on many descendants.
- Even with attempted exclusions like `:not(menu):not(menuitem):not(popover):not(window)`, the selector was still too broad in practice and resulted in transparent menus.
3. **Wayland popup restrictions complicate programmatic reproduction**
- Under Wayland, GTK may refuse to show real `GtkMenu` popups without a triggering input event (serial), producing warnings like:
- `no trigger event for menu popup`
- This makes "programmatically popup a real menu" unreliable without simulating input.
## How We Isolated It
We created a minimal stylesheet and reintroduced rules incrementally:
- `scratch.css` started essentially empty, then we added bar/pill styling only (no resets).
- Menus were fine.
- Adding the broad background reset reproduced the problem immediately.
- Replacing the broad reset with a narrow, targeted reset fixed the problem.
## Working Fix
In `scratch.css` the "pill solid" reset was replaced with a *safe reset* that does not use broad `*` descendant selectors:
- Only clear backgrounds on common bar widgets: `label`, `image`, `button` under `.outer-pad/.inner-pad/.contents`.
- Avoid `... * { ... }` rules in the bar where possible.
This preserved pill visuals while keeping SNI menus theme-driven and opaque.
## Practical Guidance (CSS)
Avoid in bar CSS:
- Broad descendants: `.something * { ... }`
- Broad transparency resets: `background-color: transparent` applied to large subtrees
Prefer:
- Targeted selectors (specific widget types/classes)
- Rules scoped to the bar window/container classes (`.taffy-box`, `.outer-pad`, `.inner-pad`) without `*`
- If you must use descendants, be extremely conservative and re-test SNI menus after changes
## Debug/Automation Helpers
This repo includes a DBus debug server (running inside taffybar) and scripts to help automate style debugging:
- DBus name: `taffybar.debug`
- Object path: `/taffybar/debug`
- Interface: `taffybar.debug`
Useful scripts / just targets:
- `just restart-scratch` runs taffybar with `TAFFYBAR_CSS_PATHS=scratch.css`
- `just restart-menu-debug` runs with `TAFFYBAR_CSS_PATHS=menu-debug.css`
- `just popup-sni-menu` attempts to popup menus (may be blocked on Wayland)
- `scripts/taffybar-screenshot-focused-monitor` takes a monitor screenshot with `grim`
Notes:
- Programmatic menu popups are not always possible on Wayland without a trigger event.
- If needed, prefer "preview window" or input simulation for fully automated capture.
## Next Steps
- Migrate the "safe reset" approach back into `taffybar.css` (or replace `taffybar.css` with the final `scratch.css` contents once complete).
- Keep `scratch.css` as a known-good bisect harness for future menu/theme regressions.

View File

@@ -1 +1,9 @@
packages: . taffybar/
packages:
.
taffybar/
taffybar/packages/gtk-scaling-image
taffybar/packages/gtk-sni-tray
taffybar/packages/gtk-strut
taffybar/packages/status-notifier-item
taffybar/packages/dbus-menu
taffybar/packages/dbus-hslogger

View File

@@ -0,0 +1,52 @@
/* End-widget palette definitions.
*
* End widget coloring is driven from the shared bar stylesheet so the runtime
* widget tree can determine which pill gets which slot without extra per-widget
* CSS providers. The actual nth-last-child selectors live in
* end-widget-solid.css, which is loaded after the main theme as a final pass.
*/
/* --- Rotation palette (5 colors) --- */
/* 1 — indigo */
@define-color end-color-1-bg rgb(50, 60, 160);
@define-color end-color-1-fg #d5d8f8;
@define-color end-color-1-border rgb(90, 100, 210);
/* 2 — purple */
@define-color end-color-2-bg rgb(110, 45, 160);
@define-color end-color-2-fg #e8d5f8;
@define-color end-color-2-border rgb(155, 85, 210);
/* 3 — emerald */
@define-color end-color-3-bg rgb(25, 130, 75);
@define-color end-color-3-fg #c8f5e0;
@define-color end-color-3-border rgb(55, 190, 115);
/* 4 — teal */
@define-color end-color-4-bg rgb(20, 120, 140);
@define-color end-color-4-fg #d0f2f8;
@define-color end-color-4-border rgb(50, 175, 200);
/* 5 — rose */
@define-color end-color-5-bg rgb(160, 40, 70);
@define-color end-color-5-fg #f8d5e0;
@define-color end-color-5-border rgb(210, 80, 115);
/* --- Shared end-widget chrome --- */
.outer-pad.end-widget,
.outer-pad.sni-tray {
background-image: none;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.10),
inset 0 0 0 1px rgba(255, 255, 255, 0.10),
0 10px 24px rgba(0, 0, 0, 0.30);
}
/* --- SNI tray (center widget, not part of the rotation) --- */
.outer-pad.sni-tray {
background-color: rgb(65, 70, 100);
border-color: rgb(110, 115, 160);
}

View File

@@ -0,0 +1,55 @@
/* Final pass overrides for end-widget pill chrome.
*
* End widgets are packed with Gtk.boxPackEnd, so the visible right-hand pills
* stay contiguous at the end of the GtkBox child list. Use nth-last-child to
* rotate colors from the right edge without depending on extra slot classes.
*/
.taffy-box > .outer-pad.end-widget:nth-last-child(5n + 1) {
background-color: @end-color-1-bg;
background-image: none;
border-color: @end-color-1-border;
color: @end-color-1-fg;
}
.taffy-box > .outer-pad.end-widget:nth-last-child(5n + 2) {
background-color: @end-color-2-bg;
background-image: none;
border-color: @end-color-2-border;
color: @end-color-2-fg;
}
.taffy-box > .outer-pad.end-widget:nth-last-child(5n + 3) {
background-color: @end-color-3-bg;
background-image: none;
border-color: @end-color-3-border;
color: @end-color-3-fg;
}
.taffy-box > .outer-pad.end-widget:nth-last-child(5n + 4) {
background-color: @end-color-4-bg;
background-image: none;
border-color: @end-color-4-border;
color: @end-color-4-fg;
}
.taffy-box > .outer-pad.end-widget:nth-last-child(5n + 5) {
background-color: @end-color-5-bg;
background-image: none;
border-color: @end-color-5-border;
color: @end-color-5-fg;
}
.outer-pad.sni-tray {
background-color: rgb(65, 70, 100);
background-image: none;
border-color: rgb(110, 115, 160);
}
.outer-pad.end-widget,
.outer-pad.sni-tray {
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.10),
inset 0 0 0 1px rgba(255, 255, 255, 0.10),
0 10px 24px rgba(0, 0, 0, 0.30);
}

View File

@@ -34,61 +34,6 @@
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-ignore-nix": {
"inputs": {
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1762808025,
"narHash": "sha256-XmjITeZNMTQXGhhww6ed/Wacy2KzD6svioyCX7pkUu4=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"ref": "master",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
@@ -112,105 +57,29 @@
"type": "github"
}
},
"gtk-sni-tray": {
"flake": false,
"locked": {
"lastModified": 1760550917,
"narHash": "sha256-oELopLiVb7D1fzYHAiw/cbfNPHLQcOJ3Rz/I1gbk8IY=",
"owner": "taffybar",
"repo": "gtk-sni-tray",
"rev": "f6d1bf5dd64ac0f532b03ae01f5e1cc051116f09",
"type": "github"
},
"original": {
"owner": "taffybar",
"ref": "master",
"repo": "gtk-sni-tray",
"type": "github"
}
},
"gtk-strut": {
"flake": false,
"locked": {
"lastModified": 1760550944,
"narHash": "sha256-S0saDsCA3aosU23jO5+jSXkbyrv7/C48o8GjsZrqF0I=",
"owner": "taffybar",
"repo": "gtk-strut",
"rev": "5e0bf7f4b93b931a9c4b4a48b5d39de3ca8f02ae",
"type": "github"
},
"original": {
"owner": "taffybar",
"ref": "master",
"repo": "gtk-strut",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1764252443,
"narHash": "sha256-U4G4dUSYWZYKtrF7/ozebD1B96at08SIhY4R9OaK1nw=",
"owner": "colonelpanic8",
"repo": "nixpkgs",
"rev": "e1fc6c25b91d3d49dd02a156237721f12dbd86b2",
"type": "github"
},
"original": {
"owner": "colonelpanic8",
"ref": "remove-gi-gtk-hs-patch",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1730768919,
"narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=",
"lastModified": 1776877367,
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc",
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1666603677,
"narHash": "sha256-apAEIj+z1iwMaMJ4tB21r/VTetfGDLDzuhXRHJknIAU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "074da18a72269cc5a6cf444dce42daea5649b2fe",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1764230294,
"narHash": "sha256-Z63xl5Scj3Y/zRBPAWq1eT68n2wBWGCIEF4waZ0bQBE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0d59e0290eefe0f12512043842d7096c4070f30e",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_2"
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
@@ -228,26 +97,19 @@
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"flake-utils": [
"taffybar",
"flake-utils"
],
"nixpkgs": [
"taffybar",
"nixpkgs"
],
"taffybar": "taffybar",
"xmonad": "xmonad"
}
},
"status-notifier-item": {
"flake": false,
"locked": {
"lastModified": 1641783528,
"narHash": "sha256-wJymJfYPFj4/r1e4kT/wt9FEsyCXo5JkkcOoozpuhag=",
"owner": "taffybar",
"repo": "status-notifier-item",
"rev": "f4f9c66ab57fc42eeed0de8cfac9f5f9d30d9dc7",
"type": "github"
},
"original": {
"owner": "taffybar",
"repo": "status-notifier-item",
"type": "github"
"xmonad": [
"taffybar",
"xmonad"
]
}
},
"systems": {
@@ -265,79 +127,26 @@
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"taffybar": {
"inputs": {
"flake-utils": "flake-utils_2",
"gtk-sni-tray": "gtk-sni-tray",
"gtk-strut": "gtk-strut",
"nixpkgs": [
"nixpkgs"
],
"status-notifier-item": "status-notifier-item",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"weeder-nix": "weeder-nix",
"xmonad": [
"xmonad"
]
"xmonad": "xmonad",
"xmonad-contrib": "xmonad-contrib"
},
"locked": {
"lastModified": 1760591109,
"narHash": "sha256-O21ayp5v2eccXdcwNjTK5ZB99ruK0Zt9CUaw5Rye42g=",
"owner": "taffybar",
"repo": "taffybar",
"rev": "b256a711416036ca124fc9b3f89e7c957535e465",
"type": "github"
"lastModified": 1777319252,
"narHash": "sha256-mPft6i8ReJAvW2LdylFI6FF6NFGa1HMa3RNbisfAsbc=",
"ref": "refs/heads/codex/fix-gdk-backend-strut-detection",
"rev": "c2cee23fc57384cd322d589944129e6c31d4f0fd",
"revCount": 2288,
"type": "git",
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
},
"original": {
"owner": "taffybar",
"ref": "master",
"repo": "taffybar",
"type": "github"
}
},
"unstable": {
"locked": {
"lastModified": 1764242076,
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
"type": "git",
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
}
},
"weeder-nix": {
@@ -349,11 +158,11 @@
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1748252779,
"narHash": "sha256-kSyD/VDUX2m9c2vcuLBT2wnpYiVSHHlP9vuDTtsAtD8=",
"lastModified": 1764753633,
"narHash": "sha256-6552zbHzdNnkREnOluE6xePIib5cc/8Nc5OnPyHORUo=",
"owner": "NorfairKing",
"repo": "weeder-nix",
"rev": "388df7a6f00220d1960118e1ad37cd86150d2c5a",
"rev": "2203c43ab9f1c4e52c2cff8e3d01bbb53159b922",
"type": "github"
},
"original": {
@@ -363,18 +172,13 @@
}
},
"xmonad": {
"inputs": {
"flake-utils": "flake-utils_3",
"git-ignore-nix": "git-ignore-nix",
"nixpkgs": "nixpkgs_4",
"unstable": "unstable"
},
"flake": false,
"locked": {
"lastModified": 1763949843,
"narHash": "sha256-aJXmGPWKjXs7cagVzUE3fd2Fpc+sXnknM446AxHgvH0=",
"lastModified": 1776502138,
"narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=",
"owner": "xmonad",
"repo": "xmonad",
"rev": "97edef37a3ae10012272d692b5e1bc9b7013fc14",
"rev": "a618fb32662e44eb5d8276a3dc1925b0233e638b",
"type": "github"
},
"original": {
@@ -383,6 +187,23 @@
"repo": "xmonad",
"type": "github"
}
},
"xmonad-contrib": {
"flake": false,
"locked": {
"lastModified": 1769258911,
"narHash": "sha256-YGEKXs4UmS5QOIELJTdCiMzTktuue+Bd3yFoIKSHuBU=",
"owner": "xmonad",
"repo": "xmonad-contrib",
"rev": "803bc3d12bdcc512ec06856c4f119d37de1ba338",
"type": "github"
},
"original": {
"owner": "xmonad",
"ref": "master",
"repo": "xmonad-contrib",
"type": "github"
}
}
},
"root": "root",

View File

@@ -1,73 +1,202 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:colonelpanic8/nixpkgs/remove-gi-gtk-hs-patch";
xmonad.url = "github:xmonad/xmonad/master";
taffybar = {
url = "github:taffybar/taffybar/master";
inputs.nixpkgs.follows = "nixpkgs";
inputs.xmonad.follows = "xmonad";
# Use the local git checkout, not a raw path snapshot, so gitignored
# build artifacts like dist-newstyle/.worktrees/.direnv don't get copied
# into flake-input store sources.
url = "git+file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar";
inputs.weeder-nix.inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
};
# Follow the vendored taffybar flake's pins so the config shell and the
# library shell mostly share their nixpkgs/Haskell dependency graph.
flake-utils.follows = "taffybar/flake-utils";
nixpkgs.follows = "taffybar/nixpkgs";
# Kept for compatibility with parent flakes that set `inputs.xmonad.follows`.
# The config flake itself does not use xmonad.lib.
xmonad = {
follows = "taffybar/xmonad";
};
};
outputs = { self, flake-utils, taffybar, nixpkgs, xmonad }:
let
hoverlay = final: prev: hself: hsuper:
{
taffybar = prev.haskell.lib.overrideCabal hsuper.taffybar (oa: {
doHaddock = false;
doCheck = false;
# Fix for GHC 9.4 where liftA2 is not in Prelude
postPatch = (oa.postPatch or "") + ''
substituteInPlace src/System/Taffybar/DBus/Client/Util.hs \
--replace-fail "import Control.Monad (forM)" \
"import Control.Monad (forM)
import Control.Applicative (liftA2)"
'';
});
# gi-gtk-hs patching is now handled by taffybar's fixVersionNamePackages overlay
imalison-taffybar = prev.haskell.lib.addPkgconfigDepends (
hself.callCabal2nix "imalison-taffybar"
(
final.lib.sourceByRegex ./.
["taffybar.hs" "imalison-taffybar.cabal"]
)
{ }) [
final.util-linux.dev
final.pcre2
final.pcre
final.libselinux.dev
final.libsepol.dev
final.fribidi.out
final.fribidi.dev
final.libthai.dev
final.libdatrie.dev
final.xorg.libXdmcp.dev
final.libxkbcommon.dev
final.libepoxy.dev
final.xorg.libXtst.out
];
outputs = {
self,
flake-utils,
taffybar,
nixpkgs,
xmonad,
...
}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
# Taffybar's flake overlay provides a set of haskell fixes we rely on.
taffybar.overlays.default
];
config.allowBroken = true;
};
# Default to the flake input so normal NixOS rebuilds stay pinned and
# reproducible. switch-local-taffybar opts into the live checkout via
# IMALISON_TAFFYBAR_LIVE_CHECKOUT when iterating on the vendored stack.
liveTaffybarCheckout = builtins.getEnv "IMALISON_TAFFYBAR_LIVE_CHECKOUT";
taffybarSourceRoot =
if liveTaffybarCheckout != ""
then /. + liveTaffybarCheckout
else taffybar.outPath;
cleanedTaffybarSource = pkgs.lib.cleanSourceWith {
src = taffybarSourceRoot;
filter = path: type:
let
relPath = pkgs.lib.removePrefix "${toString taffybarSourceRoot}/" (toString path);
excludedTopLevel = [ ".worktrees" ".direnv" "dist" "dist-newstyle" "result" ];
isExcluded = pkgs.lib.lists.any
(prefix: relPath == prefix || pkgs.lib.hasPrefix "${prefix}/" relPath)
excludedTopLevel;
in
pkgs.lib.cleanSourceFilter path type && !isExcluded;
};
localTaffybarSubdir = subdir: cleanedTaffybarSource + "/${subdir}";
hOverrides = hself: hsuper: {
dbus-menu =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "dbus-menu"
(localTaffybarSubdir "packages/dbus-menu")
{ inherit (pkgs) gtk3; })
(_: {
doCheck = false;
doHaddock = false;
# Needed for GHC 9.12 (template-haskell bound too strict upstream).
jailbreak = true;
});
status-notifier-item =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "status-notifier-item"
(localTaffybarSubdir "packages/status-notifier-item")
{ })
(_: { doCheck = false; doHaddock = false; });
gtk-scaling-image =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "gtk-scaling-image"
(localTaffybarSubdir "packages/gtk-scaling-image")
{ })
(_: { doCheck = false; doHaddock = false; });
xdg-desktop-entry =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "xdg-desktop-entry"
(localTaffybarSubdir "packages/xdg-desktop-entry")
{ })
(_: { doCheck = false; doHaddock = false; });
gtk-strut =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "gtk-strut"
(localTaffybarSubdir "packages/gtk-strut")
{ })
(_: { doCheck = false; doHaddock = false; });
gtk-sni-tray =
pkgs.haskell.lib.overrideCabal
(hself.callCabal2nix "gtk-sni-tray"
(localTaffybarSubdir "packages/gtk-sni-tray")
{ })
(_: { doCheck = false; doHaddock = false; });
dbus-hslogger =
hself.callCabal2nix "dbus-hslogger"
(localTaffybarSubdir "packages/dbus-hslogger")
{ };
# Build taffybar from our local flake input so it includes our extra
# modules (e.g. System.Taffybar.Widget.ASUS) used by this config.
taffybar = pkgs.haskell.lib.overrideCabal
(pkgs.haskell.lib.disableStaticLibraries
(hself.callCabal2nix "taffybar" cleanedTaffybarSource { inherit (pkgs) gtk3; }))
(oa: {
doHaddock = false;
doCheck = false;
# Legacy fix for older GHC (harmless on newer)
postPatch = (oa.postPatch or "") + ''
substituteInPlace src/System/Taffybar/DBus/Client/Util.hs \
--replace-fail "import Control.Monad (forM)" \
"import Control.Monad (forM)
import Control.Applicative (liftA2)"
'';
# Needed for gi-gtk-layer-shell (introspection data).
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
});
# gi-gtk-hs patching is now handled by taffybar's fixVersionNamePackages overlay
imalison-taffybar = pkgs.haskell.lib.addPkgconfigDepends (
hself.callCabal2nix "imalison-taffybar"
(pkgs.lib.sourceByRegex ./. [ "taffybar.hs" "imalison-taffybar.cabal" ])
{ }
) [
pkgs.util-linux.dev
pkgs.pcre2
pkgs.pcre
pkgs.libselinux.dev
pkgs.libsepol.dev
pkgs.fribidi.out
pkgs.fribidi.dev
pkgs.libthai.dev
pkgs.libdatrie.dev
pkgs.libxdmcp.dev
pkgs.libxkbcommon.dev
pkgs.libepoxy.dev
pkgs.libxtst.out
];
};
# Avoid depending on xmonad.lib's helper functions, since parent flakes
# can override the xmonad input via `follows` and change that API.
hpkgs = pkgs.haskell.packages.ghc98.override (old: {
overrides = pkgs.lib.composeExtensions (old.overrides or (_: _: { })) hOverrides;
});
in
{
# Expose commonly-needed ecosystem packages to callers (e.g. NixOS/home-manager
# modules) so they can run the pinned binaries directly.
packages = {
status-notifier-item = hpkgs.status-notifier-item;
};
devShell = hpkgs.shellFor {
packages = p: [ p.imalison-taffybar p.taffybar ];
nativeBuildInputs = (with hpkgs; [
alex
cabal-install
happy
# ghcid ormolu implicit-hie haskell-language-server hlint
]) ++ [
pkgs.gdk-pixbuf
pkgs.librsvg
];
shellHook = ''
# GHCi loads package DLL dependencies via the runtime linker, so it
# needs zlib on LD_LIBRARY_PATH in addition to the build-time -L flags.
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath [ pkgs.zlib ]}:''${LD_LIBRARY_PATH:-}"
if [ -z "''${GDK_PIXBUF_MODULE_FILE:-}" ]; then
export GDK_PIXBUF_MODULE_FILE="${pkgs.gdk-pixbuf}/lib/gdk-pixbuf-2.0/${pkgs.gdk-pixbuf.version}/loaders.cache"
fi
if [ -z "''${GDK_PIXBUF_MODULEDIR:-}" ]; then
export GDK_PIXBUF_MODULEDIR="${pkgs.gdk-pixbuf}/lib/gdk-pixbuf-2.0/${pkgs.gdk-pixbuf.version}/loaders"
fi
'';
};
defaultPackage = hpkgs.imalison-taffybar;
}
) // {
overlays = {
# Provide access to taffybar's overlay for callers that want it.
taffybar = taffybar.overlays.default;
};
};
defComp = { compiler = "ghc94"; };
overlay = xmonad.lib.fromHOL hoverlay defComp;
overlayList = [ taffybar.overlays.default overlay ];
in flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; overlays = overlayList; config.allowBroken = true; };
hpkgs = pkgs.lib.attrsets.getAttrFromPath (xmonad.lib.hpath defComp) pkgs;
in
{
devShell = hpkgs.shellFor {
packages = p: [ p.imalison-taffybar p.taffybar ];
nativeBuildInputs = with hpkgs; [
cabal-install
# ghcid ormolu implicit-hie haskell-language-server hlint
];
};
defaultPackage = hpkgs.imalison-taffybar;
}) // {
inherit overlay;
overlays = {
default = overlay;
taffybar = taffybar.overlays.default;
};
} ;
}

View File

@@ -21,7 +21,10 @@ executable taffybar
, containers
, directory
, filepath
, gi-gdk3
, gi-gtk3
, gi-gdkpixbuf
, gi-pango
, gtk-sni-tray
, gtk-strut
, haskell-gi-base
@@ -29,6 +32,7 @@ executable taffybar
, hslogger
, process
, split
, status-notifier-item
, text
, time
, taffybar>=3.2.0

View File

@@ -1,125 +0,0 @@
@define-color transparent rgba(0.0, 0.0, 0.0, 0.0);
@define-color white #FFFFFF;
@define-color black #000000;
@define-color taffy-blue #0c7cd5;
@define-color active-window-color @white;
@define-color urgent-window-color @taffy-blue;
@define-color font-color @white;
@define-color menu-background-color @white;
@define-color menu-font-color @black;
/* Top level styling */
.taffy-window * {
font-family: "Noto Sans", sans-serif;
font-size: 10pt;
color: @font-color;
}
.taffy-box {
border-color: @white;
border-style: solid;
border-radius: 0px;
}
.inner-pad {
padding: 3px;
}
.contents {
padding: 1px;
transition: border-color .5s, background-color .5s, border-style 3s;
border-radius: 6px;
/* This transparent border is needed because we will run in to sizing issues
without it because we only check sizing one time. */
border-color: @transparent;
border-width: 2px;
border-style: solid;
}
/* Workspaces styling */
.workspace-label {
padding-right: 3px;
padding-left: 2px;
font-size: 10pt;
}
.active .contents {
background-color: rgba(0.0, 0.0, 0.0, 0.2);
border-color: @font-color;
border-style: solid;
}
.visible .contents {
background-color: rgba(0.0, 0.0, 0.0, 0.2);
border-style: dotted;
border-color: @font-color;
}
.window-icon-container {
transition: opacity .5s, box-shadow .5s, background-color .5s;
opacity: 1;
border-radius: 5px;
}
/* This gives space for the box-shadow (they look like underlines) that follow.
This will actually affect all widgets, (not just the workspace icons), but
that is what we want since we want the icons to look the same. */
.auto-size-image, .sni-tray {
padding: 0px;
}
.window-icon-container.active {
background-color: @font-color;
}
.window-icon-container.urgent {
}
.window-icon-container.inactive .window-icon {
padding: 0px;
}
.window-icon-container.minimized .window-icon {
opacity: .3;
}
.window-icon {
opacity: 1;
transition: opacity .5s;
}
/* Button styling */
button {
all: initial;
background-color: @transparent;
border-width: 0px;
border-radius: 0px;
}
button:checked, button:hover .Contents:hover {
box-shadow: inset 0 -3px @taffy-blue;
}
/* Menu styling */
/* The ".taffy-window" prefixed selectors are needed because if they aren't present,
the top level .Taffybar selector takes precedence */
.taffy-window menuitem *, menuitem * {
color: @menu-font-color;
}
.taffy-window menuitem, menuitem {
background-color: @menu-background-color;
}
.taffy-window menuitem:hover, menuitem:hover {
background-color: @taffy-blue;
}
.taffy-window menuitem:hover > label, menuitem:hover > label {
color: @white;
}

View File

@@ -0,0 +1,18 @@
# Taffybar log levels
# Format: LoggerName: LEVEL
# Levels: DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY
#
# This file exists to selectively enable verbose logging when debugging.
# Normal/default operation should keep the log quiet (root logger is set to
# WARNING in `taffybar.hs`).
#
# Example (uncomment as-needed):
# System.Taffybar.DBus.Toggle: DEBUG
# Graphics.UI.GIGtkStrut: DEBUG
# Temporary startup debugging for tray duplication.
# Enable these selectively when investigating tray churn:
# StatusNotifier.Tray: DEBUG
# StatusNotifier.Host.Service: DEBUG
# System.Taffybar.Widget.SNITray.PrioritizedCollapsible: DEBUG
System.Taffybar.LogLevels: INFO
# System.Taffybar.Context: DEBUG

View File

@@ -0,0 +1,35 @@
/* Minimal, high-contrast menu styling for debugging CSS application. */
@define-color dbg_menu_bg #ffffcc;
@define-color dbg_menu_fg #000000;
@define-color dbg_menu_border #ff0000;
menu,
menuitem,
window.popup,
window.menu {
background-color: @dbg_menu_bg;
color: @dbg_menu_fg;
border: 2px solid @dbg_menu_border;
}
menu label,
menuitem label,
window.popup label,
window.menu label {
color: @dbg_menu_fg;
}
.dbusmenu-menu,
.dbusmenu-submenu,
.dbusmenu-item {
background-color: @dbg_menu_bg;
color: @dbg_menu_fg;
}
.dbusmenu-menu label,
.dbusmenu-submenu label,
.dbusmenu-item label {
color: @dbg_menu_fg;
}

View File

@@ -0,0 +1,7 @@
/* Compatibility wrapper.
*
* Historically this config loaded "palette.css". Keep that stable, but source
* all actual color definitions from "theme.css".
*/
@import url("theme.css");

View File

@@ -0,0 +1,10 @@
@import url("taffybar.css");
/* Host-specific density tweaks for ryzen-shine. Keep the same visual style,
* but shrink the bar back down after the upstream sizing change.
*/
@import url("taffybar.css");
.taffy-box {
font-size: 10pt;
}

Some files were not shown because too many files have changed in this diff Show More