From 82fac22687d652532720ed7f8f080c527e4044aa Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Fri, 7 Aug 2026 00:48:34 +0200 Subject: [PATCH 01/23] feat(agents): add git worktrees rule --- users/shared/programs/agents/rules.nix | 3 +++ users/shared/programs/agents/rules/worktrees.md | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 users/shared/programs/agents/rules/worktrees.md diff --git a/users/shared/programs/agents/rules.nix b/users/shared/programs/agents/rules.nix index 8e1b610..c8dbf2a 100644 --- a/users/shared/programs/agents/rules.nix +++ b/users/shared/programs/agents/rules.nix @@ -17,6 +17,9 @@ let git = { description = "Git conventions"; }; + worktrees = { + description = "Explicit naming conventions for Git worktrees"; + }; kubernetes = { description = "Kubernetes command conventions"; }; diff --git a/users/shared/programs/agents/rules/worktrees.md b/users/shared/programs/agents/rules/worktrees.md new file mode 100644 index 0000000..e5bfc30 --- /dev/null +++ b/users/shared/programs/agents/rules/worktrees.md @@ -0,0 +1,7 @@ +# Worktrees + +- Use an explicit directory name for every new worktree; never accept a generated or implicit name. +- For ticketed work, use `-` beside the primary checkout, for example `together-shaping-mosh-3603`. +- Use the ticket's configured Git branch name when available. +- Before creation, verify that neither the target directory nor branch already exists. +- Create the worktree from the intended, freshly fetched base branch. From fdfb87dc9477ebe3ca5ff5681ae04369f5cb72ac Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Fri, 7 Aug 2026 00:49:45 +0200 Subject: [PATCH 02/23] feat(agents): weekly-cycle-update skill --- .../skills/weekly-cycle-update/SKILL.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 users/shared/programs/agents/skills/weekly-cycle-update/SKILL.md diff --git a/users/shared/programs/agents/skills/weekly-cycle-update/SKILL.md b/users/shared/programs/agents/skills/weekly-cycle-update/SKILL.md new file mode 100644 index 0000000..cb5828f --- /dev/null +++ b/users/shared/programs/agents/skills/weekly-cycle-update/SKILL.md @@ -0,0 +1,56 @@ +--- +name: weekly-cycle-update +description: Produces copy-paste-ready Slack updates for weekly or end-of-cycle Model Shaping progress. Use when the user asks for a weekly update, cycle summary, completed and WIP ticket report, or next-week plan based on Linear. +--- + +# Weekly Cycle Update + +Produce the final update as rendered Markdown that can be copied from Cursor and pasted directly into Slack's rich-text composer. + +## Gather the source material + +1. Use Linear to inspect issues assigned to the user: + - completed since the previous update; + - every issue currently in an active status; + - current-cycle backlog issues relevant to next week's plans. +2. If the previous update is available, treat its timestamp as the reporting boundary and do not repeat tickets already reported as completed. +3. Use `completedAt`, not `updatedAt`, to decide whether a ticket was completed during the reporting period. +4. Read issue details when needed to describe outcomes, recent progress, remaining work, or blockers accurately. +5. Incorporate substantial non-ticket work supplied by the user, such as on-call work, reviews, design discussions, writing, or hiring. +6. Do not invent progress or plans. If Linear does not establish what changed, say so briefly or ask the user. Treat next-week plans as proposed unless the user confirms alignment with their lead. + +## Output requirements + +- Return only the finished update, with no surrounding commentary or fenced code block. +- Use standard Markdown links in the form `[MOSH-1234: Ticket title](https://linear.app/...)`. Cursor must render these as linked ticket titles before the user copies them; never emit Slack API `` syntax or bare Linear URLs. +- When a summary follows a ticket link, separate the link and em dash with a rendered non-breaking space: `[ticket](URL) — summary`. Do not use an ordinary space there; Slack removes it when pasting rich text. +- Use Markdown bold syntax (`**text**`). Cursor's rendered formatting is preserved when copied into Slack's rich-text composer. +- Use semantic nested Markdown ordered lists, not visually indented paragraphs. Every level uses a numeric Markdown marker (`1.`); Slack renders nested ordered-list levels as `1.`, `a.`, and `i.` when the rendered list is copied into its rich-text composer. +- Preserve exactly three list levels: numbered sections, lettered subsections, and roman-numeral entries. Do not use non-breaking spaces, `
` tags, or literal `a.`/`i.` prefixes. +- Keep each ticket summary to one concise sentence. +- Use the ticket's full outage title when an SRE issue would otherwise be ambiguous. +- Omit empty optional entries instead of writing filler. + +## Required structure + +```markdown +1. **Summarize your progress for the past week:** + 1. Update* and list every ticket that was completed, along with a brief summary of its outcome. + 1. [TICKET-ID: Ticket title](LINEAR_URL) — outcome. + 2. [TICKET-ID: Ticket title](LINEAR_URL) — outcome. + 2. Update and list every ticket that is still actively WIP, including a description of the progress made over last week and what remains to be done. + 1. [TICKET-ID: Ticket title](LINEAR_URL) — progress over the past week; remaining work. + 2. [TICKET-ID: Ticket title](LINEAR_URL) — progress over the past week; remaining work. + 3. If you had any substantial work that is not tracked in Linear, create and update corresponding tickets. Non-technical contributions (communication, writing, hiring, etc.) do not require tickets, but you are still required to describe them here. + 1. Contribution. + 2. Contribution. +2. **Write down your plans for the next week. Share these plans with your lead before sending to avoid drastic course corrections.** + 1. Tickets you aim to complete, including a brief description of the next steps. + 1. [TICKET-ID: Ticket title](LINEAR_URL) — next steps. + 2. Tickets you aim to make progress on, including a brief description of the next steps. + 1. [TICKET-ID: Ticket title](LINEAR_URL) — next steps. + 3. Tickets you aim to start. + 1. [TICKET-ID: Ticket title](LINEAR_URL) + 4. Current/potential blockers, if any: + 1. Blocker. +``` From 3b30f6321093d85c670816af022e1fd5214c29dc Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Fri, 7 Aug 2026 00:50:24 +0200 Subject: [PATCH 03/23] chore: update code review skill --- .../shared/programs/agents/skills/code-review/SKILL.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/users/shared/programs/agents/skills/code-review/SKILL.md b/users/shared/programs/agents/skills/code-review/SKILL.md index f35b413..6dd6fe7 100644 --- a/users/shared/programs/agents/skills/code-review/SKILL.md +++ b/users/shared/programs/agents/skills/code-review/SKILL.md @@ -61,9 +61,12 @@ For each defect: - Assign a severity: `P0` blocks all use, `P1` causes serious failures, `P2` causes ordinary functional failures, or `P3` is a limited but real defect. - Use a short, imperative title. - Cite the smallest relevant file and line range. -- Explain the concrete failure scenario and impact. +- Give a minimal concrete example showing the relevant state or input, the action that triggers the defect, and the resulting failure. +- Explain the user or operational impact. - State why the changed code causes it. -- Suggest a direction for correction when it is not obvious. +- Propose a specific correction at the right abstraction level. Include short pseudocode when it makes the solution materially clearer. + +Keep the example and correction concise when the failure and fix are straightforward. Include enough detail that the author can verify the claim and act on it without asking for a follow-up explanation. Order defects by severity. @@ -74,7 +77,8 @@ For each design concern: - Mark it as blocking or non-blocking; do not assign defect severity unless it also causes a concrete failure. - Use a short, imperative title. - Cite the smallest relevant file and line range. +- Give a concrete example using an actual or representative caller, payload, configuration, state transition, or rollout sequence that exposes the concern. - Explain the affected contract, ownership boundary, compatibility concern, or maintenance consequence. -- Suggest a direction for correction when it is not obvious. +- Propose a specific correction at the right abstraction level. Include short pseudocode when it makes the solution materially clearer. Report defects and design concerns in separate sections. If neither has actionable findings, say so plainly and mention any verification gaps. From 3da67f5f1a0af1fa818b0321ac1aac6bf3fda7bc Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Fri, 7 Aug 2026 21:09:58 +0200 Subject: [PATCH 04/23] refactor: :hammer: --- users/shared/programs/agents/rules/worktrees.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/shared/programs/agents/rules/worktrees.md b/users/shared/programs/agents/rules/worktrees.md index e5bfc30..24bbe38 100644 --- a/users/shared/programs/agents/rules/worktrees.md +++ b/users/shared/programs/agents/rules/worktrees.md @@ -1,7 +1,7 @@ # Worktrees - Use an explicit directory name for every new worktree; never accept a generated or implicit name. -- For ticketed work, use `-` beside the primary checkout, for example `together-shaping-mosh-3603`. +- For ticketed work, use `--` beside the primary checkout, for example `infra-mosh-3722-deploy-shaping-platform-api-to-qa`. - Use the ticket's configured Git branch name when available. - Before creation, verify that neither the target directory nor branch already exists. - Create the worktree from the intended, freshly fetched base branch. From aa065d917b92e901a66beaa27f3b7978a029313e Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 16:48:03 +0200 Subject: [PATCH 05/23] fix(homebrew): track brew 6.0.15 update --- flake.nix | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 8d9aeb7..59b4149 100644 --- a/flake.nix +++ b/flake.nix @@ -35,12 +35,14 @@ }; # Integrates homebrew with nix-darwin - # https://github.com/zhaofengli-wip/nix-homebrew + # https://github.com/zhaofengli/nix-homebrew nix-homebrew = { + # TODO: Return to github:zhaofengli/nix-homebrew after + # https://github.com/zhaofengli/nix-homebrew/pull/167 is merged. type = "github"; - owner = "zhaofengli-wip"; + owner = "zhaofengli"; repo = "nix-homebrew"; - inputs.nixpkgs.follows = "nixpkgs"; + ref = "pull/167/head"; }; homebrew-bundle = { From dc146eff466dc5896bdb9cdffb48a926b0a7346e Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 16:49:57 +0200 Subject: [PATCH 06/23] fix(homebrew): rename Linear cask --- modules/darwin/homebrew.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/darwin/homebrew.nix b/modules/darwin/homebrew.nix index 0b5e95c..38e4fcb 100644 --- a/modules/darwin/homebrew.nix +++ b/modules/darwin/homebrew.nix @@ -66,7 +66,7 @@ let "1password" # Productivity - "linear-linear" + "linear" "miro" # Kubernetes From 7bb5d8efec393112620eb15fb8fa6ed0d4d6031c Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 16:52:47 +0200 Subject: [PATCH 07/23] chore(deps): update flake inputs --- flake.lock | 57 +++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/flake.lock b/flake.lock index 6abbb35..0451aae 100644 --- a/flake.lock +++ b/flake.lock @@ -3,16 +3,16 @@ "brew-src": { "flake": false, "locked": { - "lastModified": 1753461463, - "narHash": "sha256-kGc7pRH0diLzKmOHsEFA8sZ9NJpgT+tqxAMsuqNd5Po=", + "lastModified": 1785710351, + "narHash": "sha256-DTL5T9+HlblsnXCEdxRpEo/2NBHD3t48BS7r+PTf090=", "owner": "Homebrew", "repo": "brew", - "rev": "4d14be89e99a45181c18e96a5f19a5b43343cc0f", + "rev": "7b0f22a4ab77567edef114c8dfc423fb96e2fbaa", "type": "github" }, "original": { "owner": "Homebrew", - "ref": "4.5.13", + "ref": "6.0.15", "repo": "brew", "type": "github" } @@ -24,11 +24,11 @@ ] }, "locked": { - "lastModified": 1756261190, - "narHash": "sha256-eiy0klFK5EVJLNilutR7grsZN/7Itj9DyD75eyOf83k=", + "lastModified": 1786031233, + "narHash": "sha256-TIDlLTLI1/pB7IqgjzcKQjpODQsZE2oII4XGG9B6KjI=", "owner": "nix-community", "repo": "home-manager", - "rev": "77f348da3176dc68b20a73dab94852a417daf361", + "rev": "7834e82588860aaf780cec1366524456a70898d7", "type": "github" }, "original": { @@ -56,11 +56,11 @@ "homebrew-cask": { "flake": false, "locked": { - "lastModified": 1756386406, - "narHash": "sha256-EEgSCHUxyyBq6Lmr7bhQ7ngv3MzNM2VQEMQXc7y3GGc=", + "lastModified": 1786199056, + "narHash": "sha256-kdPHQ0HZNvvl+RKtdZNeQFhQiJY2aqpykjycQGLHS+o=", "owner": "homebrew", "repo": "homebrew-cask", - "rev": "2072d31011279e8a84f899fa12034371442507fa", + "rev": "a2127b5bc5695f7e65197051b713a604b4de3543", "type": "github" }, "original": { @@ -72,11 +72,11 @@ "homebrew-core": { "flake": false, "locked": { - "lastModified": 1756382267, - "narHash": "sha256-dnYQcLmDWYp3EuI0rWynElrNum51wNtNwU8H6j2pWWI=", + "lastModified": 1786198709, + "narHash": "sha256-V1lIQfer7xOjbnJkBhrGd0PwOhKbuKxY9yFdCszicDY=", "owner": "homebrew", "repo": "homebrew-core", - "rev": "59aa928e65c7861ee72bf32f4845ca955e50a6b5", + "rev": "321f68b3560a035ef81a18c176dea1d7303cfb47", "type": "github" }, "original": { @@ -92,11 +92,11 @@ ] }, "locked": { - "lastModified": 1755825449, - "narHash": "sha256-XkiN4NM9Xdy59h69Pc+Vg4PxkSm9EWl6u7k6D5FZ5cM=", + "lastModified": 1785389976, + "narHash": "sha256-0tLW8Ff5yt8AH97jw4ZpFJ0OCJ122zIlgWGDmOfU/VU=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "8df64f819698c1fee0c2969696f54a843b2231e8", + "rev": "15abb8c98f336cd8bd840d71059adebabe60bf04", "type": "github" }, "original": { @@ -110,26 +110,27 @@ "brew-src": "brew-src" }, "locked": { - "lastModified": 1754250993, - "narHash": "sha256-MEin+qoQKtFC1b0f4tnQ+Z82BQWSCgh6Ef7rpmH9gig=", - "owner": "zhaofengli-wip", + "lastModified": 1785755396, + "narHash": "sha256-Pc6J04IGpHWD56vP9UaTXEfZJ08i6UxL2Gz84iTocHo=", + "owner": "zhaofengli", "repo": "nix-homebrew", - "rev": "314d057294e79bc2596972126b84c6f9f144499a", + "rev": "212a0902eac8642c8e01a990c9031250d9408a74", "type": "github" }, "original": { - "owner": "zhaofengli-wip", + "owner": "zhaofengli", + "ref": "pull/167/head", "repo": "nix-homebrew", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1756288264, - "narHash": "sha256-Om8adB1lfkU7D33VpR+/haZ2gI5r3Q+ZbIPzE5sYnwE=", + "lastModified": 1785975029, + "narHash": "sha256-X44cn5rzytELc3NNoQsh0aLkjWA/QzPfc6HPQmsG3sU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ddd1826f294a0ee5fdc198ab72c8306a0ea73aa9", + "rev": "70ce234312134a463ba7728e94da2486a1d237ac", "type": "github" }, "original": { @@ -142,11 +143,11 @@ "private-config": { "flake": false, "locked": { - "lastModified": 1785509883, - "narHash": "sha256-M4r2pZxRpdoou9/cvHEm5LYWwEKX+C1pkBxUsyKEHZo=", + "lastModified": 1785627204, + "narHash": "sha256-yNX0lSW4Y4/aXArnw+IwG0z3eqzp7t9COMQJMI+B54A=", "ref": "refs/heads/master", - "rev": "458e856611a2bf34b981b11cdc115ec82b45a5b5", - "revCount": 2, + "rev": "e1760e4b5d6b87683b12613332d8eb6782befe07", + "revCount": 3, "type": "git", "url": "ssh://git@github.com/khaykingleb/dotfiles-private.git" }, From 05bb789ba4bc1bb8e0f8b4ed7499151f4dde7ba2 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 16:53:06 +0200 Subject: [PATCH 08/23] refactor(home-manager): migrate deprecated Git and SSH options --- users/shared/programs/git.nix | 19 +++++++----- users/shared/programs/ssh.nix | 32 ++++++++++---------- users/shared/programs/vscode/settings.json | 35 ++++++++++++---------- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/users/shared/programs/git.nix b/users/shared/programs/git.nix index 16d3f1d..7fb193e 100644 --- a/users/shared/programs/git.nix +++ b/users/shared/programs/git.nix @@ -3,8 +3,6 @@ { programs.git = { enable = true; - userName = "Gleb Khaykin"; - userEmail = "khaykingleb@gmail.com"; # Use git-lfs for large files lfs = { @@ -12,12 +10,11 @@ skipSmudge = true; }; - # Use diff-so-fancy for better git diffs - diff-so-fancy = { - enable = true; - }; - - extraConfig = { + settings = { + user = { + name = "Gleb Khaykin"; + email = "khaykingleb@gmail.com"; + }; # Push to remote automatically if it's not set # (you don't need to run `git push --set-upstream origin ` every time) push.autoSetupRemote = true; @@ -27,4 +24,10 @@ branch.autoSetupMerge = true; }; }; + + # Use diff-so-fancy for better git diffs + programs.diff-so-fancy = { + enable = true; + enableGitIntegration = true; + }; } diff --git a/users/shared/programs/ssh.nix b/users/shared/programs/ssh.nix index 6d8157b..7b0d8b0 100644 --- a/users/shared/programs/ssh.nix +++ b/users/shared/programs/ssh.nix @@ -3,32 +3,30 @@ programs.ssh = { enable = true; enableDefaultConfig = false; - matchBlocks = { + settings = { "*.cloud.together.ai" = { - user = "gkhaykin"; + User = "gkhaykin"; }; "*.vast.ai" = { - identityFile = "${config.home.homeDirectory}/.ssh/vastai"; + IdentityFile = "${config.home.homeDirectory}/.ssh/vastai"; }; "alabama alaska" = { - user = "gkhaykin"; - proxyCommand = "/opt/homebrew/bin/cloudflared access ssh --hostname %h-dc10.myna.ninja/ssh"; + User = "gkhaykin"; + ProxyCommand = "/opt/homebrew/bin/cloudflared access ssh --hostname %h-dc10.myna.ninja/ssh"; }; "github.com *.github.com" = { - hostname = "ssh.github.com"; - port = 443; - user = "git"; - identityFile = "${config.home.homeDirectory}/.ssh/id_rsa"; - identitiesOnly = true; - extraOptions = { - "AddKeysToAgent" = "yes"; - "UseKeychain" = "yes"; - }; + HostName = "ssh.github.com"; + Port = 443; + User = "git"; + IdentityFile = "${config.home.homeDirectory}/.ssh/id_rsa"; + IdentitiesOnly = true; + AddKeysToAgent = "yes"; + UseKeychain = "yes"; }; "red" = { - hostname = "64.247.196.50"; - user = "shadeform"; - identityFile = "${config.home.homeDirectory}/Desktop/projects/iclerk/gpu-access/shadeform_private_key.pem"; + HostName = "64.247.196.50"; + User = "shadeform"; + IdentityFile = "${config.home.homeDirectory}/Desktop/projects/iclerk/gpu-access/shadeform_private_key.pem"; }; }; }; diff --git a/users/shared/programs/vscode/settings.json b/users/shared/programs/vscode/settings.json index b9a558d..81145e0 100644 --- a/users/shared/programs/vscode/settings.json +++ b/users/shared/programs/vscode/settings.json @@ -1,13 +1,10 @@ { /* UI & Interface */ "editor.formatOnPaste": true, - "editor.formatOnSave": true, + "editor.formatOnSave": false, "editor.multiCursorModifier": "ctrlCmd", "editor.fontSize": 13, - "editor.rulers": [ - 79, - 100 - ], + "editor.rulers": [79, 100], /* Files */ "files.autoSave": "onFocusChange", "files.insertFinalNewline": true, @@ -17,7 +14,7 @@ "workbench.colorTheme": "Default Dark Modern", "workbench.iconTheme": "material-icon-theme", "workbench.editorAssociations": { - "{git,gitlens,git-graph}:/**/*.{md,csv,svg}": "default" + "{git,gitlens,git-graph}:/**/*.{md,csv,svg}": "default", }, /* Terminal */ "terminal.integrated.fontFamily": "MesloLGM Nerd Font Mono", @@ -27,15 +24,14 @@ "notebook.output.textLineLimit": 120, /* Nix */ "nix.enableLanguageServer": true, - "nix.serverPath": "nixd", + // Absolute path: GUI-launched editors may not have the Nix profile on PATH. + "nix.serverPath": "/run/current-system/sw/bin/nixd", "nix.serverSettings": { "nixd": { "formatting": { - "command": [ - "nixfmt" - ] - } - } + "command": ["nixfmt"], + }, + }, }, /* LaTeX */ "latex-workshop.formatting.latex": "tex-fmt", @@ -44,11 +40,11 @@ "editor.insertSpaces": true, "editor.tabSize": 2, "editor.autoIndent": "advanced", - "editor.defaultFormatter": "redhat.vscode-yaml" + "editor.defaultFormatter": "redhat.vscode-yaml", }, /* GitHub Actions */ "[github-actions-workflow]": { - "editor.defaultFormatter": "redhat.vscode-yaml" + "editor.defaultFormatter": "redhat.vscode-yaml", }, /* YAML */ "yaml.disableSchemaDetection": [ @@ -57,9 +53,16 @@ "**/.gitea/workflows/*.yml", "**/.gitea/workflows/*.yaml", "**/.forgejo/workflows/*.yml", - "**/.forgejo/workflows/*.yaml" + "**/.forgejo/workflows/*.yaml", ], /* Python */ "python-envs.alwaysUseUv": true, - "python.defaultInterpreterPath": "/opt/homebrew/bin/python3" + "python.defaultInterpreterPath": "/opt/homebrew/bin/python3", + /* Kubernetes */ + // GUI-launched editors don't inherit the shell PATH, so point at the asdf shim + // (it execs asdf via an absolute nix-store path and needs no environment). + "vs-kubernetes": { + "vs-kubernetes.kubectl-path": "/Users/gkhaykin/.asdf/shims/kubectl", + }, + "claudeCode.preferredLocation": "panel", } From 7954c82815587cb52a26cc35e5029a635f3fa2e7 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 16:57:52 +0200 Subject: [PATCH 09/23] feat(gh): manage stack extension declaratively --- modules/common/packages.nix | 3 --- users/shared/programs/default.nix | 1 + users/shared/programs/gh.nix | 9 +++++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 users/shared/programs/gh.nix diff --git a/modules/common/packages.nix b/modules/common/packages.nix index fbaa8eb..e9a713f 100644 --- a/modules/common/packages.nix +++ b/modules/common/packages.nix @@ -7,9 +7,6 @@ neovim # VCS - git - diff-so-fancy # git diff formatter - gh # github cli act # run github actions locally dvc # data version control for ML diff --git a/users/shared/programs/default.nix b/users/shared/programs/default.nix index a3e6cd5..29e11b1 100644 --- a/users/shared/programs/default.nix +++ b/users/shared/programs/default.nix @@ -13,6 +13,7 @@ ./conda.nix ./cursor.nix ./direnv.nix + ./gh.nix ./git.nix ./krew.nix ./ssh.nix diff --git a/users/shared/programs/gh.nix b/users/shared/programs/gh.nix new file mode 100644 index 0000000..67a306a --- /dev/null +++ b/users/shared/programs/gh.nix @@ -0,0 +1,9 @@ +{ pkgs, ... }: +{ + programs.gh = { + enable = true; + extensions = with pkgs; [ + gh-stack + ]; + }; +} From 952735bbede8250e9fc01bcccc1f3030dd0782f4 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 17:24:44 +0200 Subject: [PATCH 10/23] refactor(asdf): migrate to Go implementation --- users/shared/completions.nix | 5 ----- users/shared/programs/asdf.nix | 27 +++++++++++++++------------ users/shared/scripts/asdf.sh | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/users/shared/completions.nix b/users/shared/completions.nix index df7e515..d77e655 100644 --- a/users/shared/completions.nix +++ b/users/shared/completions.nix @@ -49,11 +49,6 @@ in FPATH="${completionsDirectory}:$FPATH" # <<< Generated completions - # >>> ASDF completions - # https://asdf-vm.com/guide/getting-started-legacy.html - FPATH="$HOME/.asdf/completions:$FPATH" - # <<< ASDF completions - autoload -Uz compinit compinit diff --git a/users/shared/programs/asdf.nix b/users/shared/programs/asdf.nix index 03f7bc8..aac6145 100644 --- a/users/shared/programs/asdf.nix +++ b/users/shared/programs/asdf.nix @@ -1,16 +1,19 @@ # Runtime version manager -{ lib, pkgs, ... }: { - home.packages = with pkgs; [ - asdf-vm - ]; - programs.zsh = { - # NOTE: https://asdf-vm.com/guide/getting-started.html - initContent = lib.mkAfter '' - # >>> ASDF - . "${pkgs.asdf-vm}/share/asdf-vm/asdf.sh" - export PATH="$HOME/.asdf/shims:$PATH" - # <<< ASDF - ''; + config, + lib, + pkgs, + ... +}: +{ + home = { + packages = [ pkgs.asdf-vm ]; + sessionVariables.ASDF_DATA_DIR = "${config.home.homeDirectory}/.asdf"; + sessionPath = [ "${config.home.homeDirectory}/.asdf/shims" ]; }; + + # Restore asdf shim precedence after macOS path_helper moves system paths to the front + programs.zsh.initContent = lib.mkAfter '' + path=("$ASDF_DATA_DIR/shims" $path) + ''; } diff --git a/users/shared/scripts/asdf.sh b/users/shared/scripts/asdf.sh index e2c5918..02d64ec 100755 --- a/users/shared/scripts/asdf.sh +++ b/users/shared/scripts/asdf.sh @@ -6,7 +6,7 @@ install_plugin() { echo "Installing $plugin $version..." asdf plugin add "$plugin" 2>/dev/null || true asdf install "$plugin" "$version" || echo "Failed to install $plugin $version" - asdf global "$plugin" "$version" + asdf set --home "$plugin" "$version" } plugins=( From 1fa23e1b152f7787e8142fae31ad34bf0618f047 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 17:46:26 +0200 Subject: [PATCH 11/23] feat(aws): add profile selection helper --- users/shared/programs/awscli.nix | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/users/shared/programs/awscli.nix b/users/shared/programs/awscli.nix index 956b5e4..add280c 100644 --- a/users/shared/programs/awscli.nix +++ b/users/shared/programs/awscli.nix @@ -1,8 +1,32 @@ -{ ... }: +{ lib, ... }: { - programs.zsh.initContent = '' + programs.zsh.initContent = lib.mkAfter '' # >>> AWS export AWS_SDK_LOAD_CONFIG=1 + + # Select a profile and authenticate only when cached credentials are unavailable. + aws-profile() { + if (( $# != 1 )); then + echo "usage: aws-profile " >&2 + return 2 + fi + + local profile="$1" + if ! aws configure export-credentials \ + --profile "$profile" \ + --format process >/dev/null 2>&1; then + aws sso login --profile "$profile" || return + fi + + export AWS_PROFILE="$profile" + } + + _aws-profile() { + local -a profiles + profiles=($(aws configure list-profiles 2>/dev/null)) + _describe "AWS profile" profiles + } + compdef _aws-profile aws-profile # <<< AWS ''; From 7443ec82a36ffc5abccce332f8744bca09d961cd Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 18:08:09 +0200 Subject: [PATCH 12/23] refactor: :hammer: --- users/shared/completions.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/users/shared/completions.nix b/users/shared/completions.nix index d77e655..b3880c2 100644 --- a/users/shared/completions.nix +++ b/users/shared/completions.nix @@ -1,4 +1,9 @@ -{ config, lib, ... }: +{ + config, + lib, + pkgs, + ... +}: let completionsDirectory = "${config.home.homeDirectory}/.zsh/completions"; @@ -22,8 +27,8 @@ in # command interfaces and their completions. home.activation.generateZshCompletions = lib.hm.dag.entryAfter [ "writeBoundary" ] '' ( - # asdf shims and Docker Desktop live outside the Nix activation's PATH. - PATH="$HOME/.asdf/shims:/usr/local/bin:$PATH" + # asdf, its shims, and Docker Desktop live outside the activation PATH. + PATH="$HOME/.asdf/shims:${lib.makeBinPath [ pkgs.asdf-vm ]}:/usr/local/bin:$PATH" generate_zsh_completion() { local file="_$1" From 756e684ac809a4d7c80e4d509046e0ef6435637e Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 18:58:48 +0200 Subject: [PATCH 13/23] feat(finicky): route work links to Chrome work profile --- modules/darwin/homebrew.nix | 1 + users/gkhaykin/default.nix | 7 ++++++- users/gkhaykin/programs/finicky.nix | 10 ++++++++++ users/gkhaykin/programs/finicky.ts | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 users/gkhaykin/programs/finicky.nix create mode 100644 users/gkhaykin/programs/finicky.ts diff --git a/modules/darwin/homebrew.nix b/modules/darwin/homebrew.nix index 38e4fcb..fcc7648 100644 --- a/modules/darwin/homebrew.nix +++ b/modules/darwin/homebrew.nix @@ -66,6 +66,7 @@ let "1password" # Productivity + "finicky" "linear" "miro" diff --git a/users/gkhaykin/default.nix b/users/gkhaykin/default.nix index ba54370..f4443ab 100644 --- a/users/gkhaykin/default.nix +++ b/users/gkhaykin/default.nix @@ -1,8 +1,13 @@ -{ inputs, pkgs, ... }: +{ + inputs, + pkgs, + ... +}: { imports = [ ../shared/programs ./onepassword.nix + ./programs/finicky.nix ./programs/vscode ../shared/completions.nix diff --git a/users/gkhaykin/programs/finicky.nix b/users/gkhaykin/programs/finicky.nix new file mode 100644 index 0000000..28a7e65 --- /dev/null +++ b/users/gkhaykin/programs/finicky.nix @@ -0,0 +1,10 @@ +{ lib, pkgs, ... }: +lib.mkIf pkgs.stdenv.hostPlatform.isDarwin { + # Home Manager has no default-browser option, so use duti (a macOS + # LaunchServices CLI) to register Finicky as the HTTP(S) URL handler. + home.packages = [ pkgs.duti ]; + home.file.".finicky.ts".source = ./finicky.ts; + home.activation.setFinickyAsDefaultBrowser = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + run ${lib.getExe pkgs.duti} -s se.johnste.finicky http + ''; +} diff --git a/users/gkhaykin/programs/finicky.ts b/users/gkhaykin/programs/finicky.ts new file mode 100644 index 0000000..a26a9f1 --- /dev/null +++ b/users/gkhaykin/programs/finicky.ts @@ -0,0 +1,21 @@ +// @ts-ignore Finicky provides its type declarations after installation. +import type { FinickyConfig } from "/Applications/Finicky.app/Contents/Resources/finicky.d.ts"; + +export default { + defaultBrowser: "Google Chrome", + handlers: [ + { + match: finicky.matchHostnames([ + "together.ai", + /\.together\.ai$/, + "together-ai.okta.com", + /\.awsapps\.com$/, + /^oidc\..+\.amazonaws\.com$/, + ]), + browser: { + name: "Google Chrome", + profile: "Together", + }, + }, + ], +} satisfies FinickyConfig; From 724d6bbaff23f50aad073695ccedf26c7bee04a7 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 19:57:40 +0200 Subject: [PATCH 14/23] feat(kubie): add context name completion --- users/shared/programs/default.nix | 1 + users/shared/programs/kubie.nix | 34 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 users/shared/programs/kubie.nix diff --git a/users/shared/programs/default.nix b/users/shared/programs/default.nix index 29e11b1..bf2b888 100644 --- a/users/shared/programs/default.nix +++ b/users/shared/programs/default.nix @@ -16,6 +16,7 @@ ./gh.nix ./git.nix ./krew.nix + ./kubie.nix ./ssh.nix ./tmux.nix ./zoxide.nix diff --git a/users/shared/programs/kubie.nix b/users/shared/programs/kubie.nix new file mode 100644 index 0000000..5be6379 --- /dev/null +++ b/users/shared/programs/kubie.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + pkgs, + ... +}: +let + completionsDirectory = "${config.home.homeDirectory}/.zsh/completions"; + kubieCompletion = "${completionsDirectory}/_kubie"; +in +{ + # TODO: Remove when Kubie supports dynamic context completion. + # https://github.com/kubie-org/kubie/pull/374 + home.file.".zsh/completions/_kubie_contexts".text = '' + #autoload + local -a contexts + contexts=("''${(@f)$(kubie ctx 2>/dev/null)}") + _describe "Kubie context" contexts + ''; + + home.activation.patchKubieContextCompletion = lib.hm.dag.entryAfter [ "generateZshCompletions" ] '' + kubie_completion=${lib.escapeShellArg kubieCompletion} + if [[ -v DRY_RUN ]]; then + verboseEcho "Would add dynamic context completion to $kubie_completion" + elif [[ -f "$kubie_completion" ]]; then + ${lib.getExe' pkgs.gnused "sed"} -i \ + "/Name of the context to enter/ s/:_default'/:_kubie_contexts'/" \ + "$kubie_completion" + if [[ "$(<"$kubie_completion")" != *":_kubie_contexts'"* ]]; then + echo "warning: failed to add Kubie context completion" >&2 + fi + fi + ''; +} From 9966a281efb29805ea3a9f0291d22faea90633b9 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:28:59 +0200 Subject: [PATCH 15/23] refactor(tooling): streamline runtime version management --- justfile | 6 +- users/shared/programs/asdf/.tool-versions | 40 ++++++++++ .../programs/{asdf.nix => asdf/default.nix} | 2 + users/shared/programs/asdf/install.sh | 79 +++++++++++++++++++ users/shared/programs/conda.nix | 29 ------- users/shared/programs/default.nix | 3 +- users/shared/programs/vscode/settings.json | 10 ++- users/shared/scripts/asdf.sh | 53 ------------- users/shared/scripts/conda.sh | 14 ---- 9 files changed, 132 insertions(+), 104 deletions(-) create mode 100644 users/shared/programs/asdf/.tool-versions rename users/shared/programs/{asdf.nix => asdf/default.nix} (72%) create mode 100755 users/shared/programs/asdf/install.sh delete mode 100644 users/shared/programs/conda.nix delete mode 100755 users/shared/scripts/asdf.sh delete mode 100755 users/shared/scripts/conda.sh diff --git a/justfile b/justfile index b44340d..187a761 100644 --- a/justfile +++ b/justfile @@ -66,6 +66,6 @@ pre-commit-run: @pre-commit run --all-files [group('misc')] -[doc('Install asdf plugins')] -asdf-install: - @./users/shared/scripts/asdf.sh +[doc('Reconcile asdf plugins and tool versions')] +asdf-sync: + @./users/shared/programs/asdf/install.sh diff --git a/users/shared/programs/asdf/.tool-versions b/users/shared/programs/asdf/.tool-versions new file mode 100644 index 0000000..2a0ea82 --- /dev/null +++ b/users/shared/programs/asdf/.tool-versions @@ -0,0 +1,40 @@ +# Languages and runtimes +python 3.12.9 +rust 1.97.1 +golang 1.25.5 +nodejs 23.6.1 + +# Package managers +uv 0.10.8 +pnpm 9.15.5 +yarn 1.22.22 + +# Infrastructure as code +terraform 1.12.1 +opentofu 1.9.0 +tflint 0.55.1 +terraform-docs 0.19.0 + +# Kubernetes +kubectl 1.36.3 +k3s 1.36.3+k3s1 +helm 3.17.0 +k3d 5.8.1 +k9s 0.50.9 +krew 0.4.5 +kubectx 0.9.5 +kubie 0.28.0 +kubeconform 0.7.0 +kube-linter 0.7.2 +kustomize 5.8.1 +tilt 0.33.22 +argocd 3.5.0 + +# Backend and AI +supabase-cli 2.12.1 +ollama 0.6.3 + +# Development tools +pre-commit 4.3.0 +buf 1.61.0 +just 1.46.0 diff --git a/users/shared/programs/asdf.nix b/users/shared/programs/asdf/default.nix similarity index 72% rename from users/shared/programs/asdf.nix rename to users/shared/programs/asdf/default.nix index aac6145..ee5febf 100644 --- a/users/shared/programs/asdf.nix +++ b/users/shared/programs/asdf/default.nix @@ -8,6 +8,8 @@ { home = { packages = [ pkgs.asdf-vm ]; + file.".tool-versions".source = + config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/.config/dotfiles/users/shared/programs/asdf/.tool-versions"; sessionVariables.ASDF_DATA_DIR = "${config.home.homeDirectory}/.asdf"; sessionPath = [ "${config.home.homeDirectory}/.asdf/shims" ]; }; diff --git a/users/shared/programs/asdf/install.sh b/users/shared/programs/asdf/install.sh new file mode 100755 index 0000000..687e6ae --- /dev/null +++ b/users/shared/programs/asdf/install.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +set -Eeuo pipefail + +tool_versions_file="$HOME/.tool-versions" +changes=0 + +plugin_installed() { + local expected=$1 + local installed + + while IFS= read -r installed; do + [[ "$installed" == "$expected" ]] && return 0 + done < <(asdf plugin list) + + return 1 +} + +krew_plugin_installed() { + local expected=$1 + local installed + + while IFS= read -r installed; do + [[ "$installed" == "$expected" ]] && return 0 + done < <(kubectl krew list) + + return 1 +} + +command -v asdf >/dev/null 2>&1 || { + echo "asdf is not available" >&2 + exit 1 +} +[[ -r "$tool_versions_file" ]] || { + echo "$tool_versions_file is not readable; apply the Home Manager configuration first" >&2 + exit 1 +} + +while IFS= read -r line || [[ -n "$line" ]]; do + line=${line%%#*} + read -r -a fields <<<"$line" + ((${#fields[@]} > 1)) || continue + + plugin=${fields[0]} + if ! plugin_installed "$plugin"; then + echo "Adding asdf plugin: $plugin" + if [[ "$plugin" == "supabase-cli" ]]; then + asdf plugin add "$plugin" https://github.com/gavinying/asdf-supabase-cli.git + else + asdf plugin add "$plugin" + fi + changes=$((changes + 1)) + fi + + for version in "${fields[@]:1}"; do + [[ "$version" == "system" || "$version" == path:* ]] && continue + asdf where "$plugin" "$version" >/dev/null 2>&1 && continue + + echo "Installing $plugin $version" + asdf install "$plugin" "$version" + changes=$((changes + 1)) + done +done <"$tool_versions_file" + +asdf reshim + +if ! krew_plugin_installed neat; then + kubectl krew install neat + changes=$((changes + 1)) +fi + +echo +if ((changes == 0)); then + echo "asdf is already in sync" +elif ((changes == 1)); then + echo "asdf sync complete (1 change)" +else + echo "asdf sync complete ($changes changes)" +fi diff --git a/users/shared/programs/conda.nix b/users/shared/programs/conda.nix deleted file mode 100644 index 78a62e0..0000000 --- a/users/shared/programs/conda.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ config, pkgs, ... }: -{ - # home.activation.conda = - # if pkgs.stdenv.hostPlatform.isDarwin then '' - # sh ${config.home.homeDirectory}/.config/dotfiles/users/shared/scripts/conda.sh install_conda_macos_silicon - # '' else '' - # sh ${config.home.homeDirectory}/.config/dotfiles/users/shared/scripts/conda.sh install_conda_linux - # ''; - # TODO(khaykingleb): conda doesn't work on my work mac - # programs.zsh.initContent = '' - # # >>> Conda - # __conda_setup="$(${config.home.homeDirectory}/anaconda3/bin/conda 'shell.zsh' 'hook' 2> /dev/null)" - # if [ $? -eq 0 ]; then - # eval "$__conda_setup" - # else - # if [ -f "${config.home.homeDirectory}/anaconda3/etc/profile.d/conda.sh" ]; then - # . "${config.home.homeDirectory}/anaconda3/etc/profile.d/conda.sh" - # else - # export PATH="${config.home.homeDirectory}/anaconda3/bin:$PATH" - # fi - # fi - # unset __conda_setup - - # # Force deactivation of base environment for conda - # # (otherwise, it's activated in Cursor IDE for some reason) - # conda deactivate - # # <<< Conda - # ''; -} diff --git a/users/shared/programs/default.nix b/users/shared/programs/default.nix index bf2b888..30ca854 100644 --- a/users/shared/programs/default.nix +++ b/users/shared/programs/default.nix @@ -6,11 +6,10 @@ ./vscode ./zsh - ./asdf.nix + ./asdf ./atuin.nix ./awscli.nix ./claude.nix - ./conda.nix ./cursor.nix ./direnv.nix ./gh.nix diff --git a/users/shared/programs/vscode/settings.json b/users/shared/programs/vscode/settings.json index 81145e0..d116728 100644 --- a/users/shared/programs/vscode/settings.json +++ b/users/shared/programs/vscode/settings.json @@ -7,11 +7,14 @@ "editor.rulers": [79, 100], /* Files */ "files.autoSave": "onFocusChange", + "files.associations": { + "**/.tool-versions": "plaintext" + }, "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, /* Theme & Icons */ - "workbench.colorTheme": "Default Dark Modern", + "workbench.colorTheme": "Dark Modern", "workbench.iconTheme": "material-icon-theme", "workbench.editorAssociations": { "{git,gitlens,git-graph}:/**/*.{md,csv,svg}": "default", @@ -20,6 +23,8 @@ "terminal.integrated.fontFamily": "MesloLGM Nerd Font Mono", "terminal.integrated.fontSize": 13, "terminal.integrated.macOptionIsMeta": true, + /* Claude Code */ + "claudeCode.preferredLocation": "panel", /* Jupyter */ "notebook.output.textLineLimit": 120, /* Nix */ @@ -53,7 +58,7 @@ "**/.gitea/workflows/*.yml", "**/.gitea/workflows/*.yaml", "**/.forgejo/workflows/*.yml", - "**/.forgejo/workflows/*.yaml", + "**/.forgejo/workflows/*.yaml" ], /* Python */ "python-envs.alwaysUseUv": true, @@ -64,5 +69,4 @@ "vs-kubernetes": { "vs-kubernetes.kubectl-path": "/Users/gkhaykin/.asdf/shims/kubectl", }, - "claudeCode.preferredLocation": "panel", } diff --git a/users/shared/scripts/asdf.sh b/users/shared/scripts/asdf.sh deleted file mode 100755 index 02d64ec..0000000 --- a/users/shared/scripts/asdf.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -install_plugin() { - plugin=$1 - version=$2 - echo "Installing $plugin $version..." - asdf plugin add "$plugin" 2>/dev/null || true - asdf install "$plugin" "$version" || echo "Failed to install $plugin $version" - asdf set --home "$plugin" "$version" -} - -plugins=( - "python 3.12.9" # programming language - "uv 0.10.8" # python package manager - "rust 1.97.1" # programming language - "golang 1.25.5" # programming language - "terraform 1.12.1" # IaC provisioning - "opentofu 1.9.0" # fork of terraform since license changed - "tflint 0.55.1" # terraform linter - "terraform-docs 0.19.0" # terraform documentation generator - "kubectl 1.32.1" # k8s cli - "helm 3.17.0" # k8s package manager - "k3s 1.31.5+k3s1" # lightweight k8s - "k3d 5.8.1" # wrapper to run k3s in docker - "k9s 0.50.9" # UI to interact with k8s clusters - "krew 0.4.5" # k8s plugin manager - "kubectx 0.9.5" # k8s context switcher - "kubie 0.28.0" # more powerful k8s context switcher - "kubeconform 0.7.0" # k8s schema validator - "kube-linter 0.7.2" # k8s linter - "kustomize 5.6.0" # k8s manifest generator - "tilt 0.33.22" # k8s developer experience - "argocd 3.1.8" # k8s gitops - "awscli 2.23.3" # aws cli - "nodejs 23.6.1" # javascript runtime - "pnpm 9.15.5" # javascript package manager - "yarn 1.22.22" # javascript package manager - "supabase-cli 2.12.1" # baas with postgres - "postgres 17.2" # postgres database client - "ollama 0.6.3" # local llm hosting - "pre-commit 4.3.0" # git commit hook manager - "direnv 2.35.0" # environment manager - "buf 1.61.0" # protobuf tooling - "just 1.46.0" # building tool, alternative to make -) -asdf plugin add supabase-cli https://github.com/gavinying/asdf-supabase-cli.git 2>/dev/null || true -for plugin in "${plugins[@]}"; do - IFS=' ' read -r name version <<<"$plugin" - install_plugin "$name" "$version" -done - -# Install krew plugins for k8s -kubectl krew install neat diff --git a/users/shared/scripts/conda.sh b/users/shared/scripts/conda.sh deleted file mode 100755 index 8ff2f9c..0000000 --- a/users/shared/scripts/conda.sh +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/bash - -install_conda_macos_silicon() { - if [ ! -d "$HOME/anaconda3" ]; then - echo "Installing Anaconda..." - /usr/bin/curl -O https://repo.anaconda.com/archive/Anaconda3-2024.10-1-MacOSX-arm64.sh && - sh Anaconda3-2024.10-1-MacOSX-arm64.sh -b -p ~/anaconda3 && - rm Anaconda3-2024.10-1-MacOSX-arm64.sh - else - echo "Anaconda is already installed, skipping installation..." - fi -} - -"$@" From a86b4dcdd6c3c22640f178da5c1cbecaed595bcd Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:34:23 +0200 Subject: [PATCH 16/23] refactor: :hammer: --- users/shared/programs/asdf/install.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/users/shared/programs/asdf/install.sh b/users/shared/programs/asdf/install.sh index 687e6ae..87fdbf7 100755 --- a/users/shared/programs/asdf/install.sh +++ b/users/shared/programs/asdf/install.sh @@ -5,6 +5,13 @@ set -Eeuo pipefail tool_versions_file="$HOME/.tool-versions" changes=0 +announce() { + if ((changes > 0)); then + echo + fi + echo "$1" +} + plugin_installed() { local expected=$1 local installed @@ -43,7 +50,7 @@ while IFS= read -r line || [[ -n "$line" ]]; do plugin=${fields[0]} if ! plugin_installed "$plugin"; then - echo "Adding asdf plugin: $plugin" + announce "Adding asdf plugin: $plugin" if [[ "$plugin" == "supabase-cli" ]]; then asdf plugin add "$plugin" https://github.com/gavinying/asdf-supabase-cli.git else @@ -56,7 +63,7 @@ while IFS= read -r line || [[ -n "$line" ]]; do [[ "$version" == "system" || "$version" == path:* ]] && continue asdf where "$plugin" "$version" >/dev/null 2>&1 && continue - echo "Installing $plugin $version" + announce "Installing $plugin $version" asdf install "$plugin" "$version" changes=$((changes + 1)) done @@ -65,15 +72,17 @@ done <"$tool_versions_file" asdf reshim if ! krew_plugin_installed neat; then + announce "Installing Krew plugin: neat" kubectl krew install neat changes=$((changes + 1)) fi -echo if ((changes == 0)); then echo "asdf is already in sync" elif ((changes == 1)); then + echo echo "asdf sync complete (1 change)" else + echo echo "asdf sync complete ($changes changes)" fi From 8553b9d9dbfb956a25393271a985eaafcc9ee671 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:34:47 +0200 Subject: [PATCH 17/23] feat(git): prune during fetching --- users/shared/programs/git.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/shared/programs/git.nix b/users/shared/programs/git.nix index 7fb193e..a520888 100644 --- a/users/shared/programs/git.nix +++ b/users/shared/programs/git.nix @@ -20,8 +20,8 @@ push.autoSetupRemote = true; # Pull with rebase instead of merge pull.rebase = true; - # Automatically setup merge branches when pulling - branch.autoSetupMerge = true; + # Prune remote branches that have been deleted on the remote + fetch.prune = true; }; }; From 88e120de3be9cfee61be841818d2eacbe261ae72 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:41:53 +0200 Subject: [PATCH 18/23] feat(git): cleanup-branches alias introduction --- users/shared/programs/git.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/users/shared/programs/git.nix b/users/shared/programs/git.nix index a520888..c81a732 100644 --- a/users/shared/programs/git.nix +++ b/users/shared/programs/git.nix @@ -22,6 +22,18 @@ pull.rebase = true; # Prune remote branches that have been deleted on the remote fetch.prune = true; + # Delete only merged local branches whose upstream was removed. + alias.cleanup-branches = '' + !f() { + git fetch --prune || return + git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads | + while read -r branch tracking; do + if [ "$tracking" = "[gone]" ]; then + git branch -d "$branch" + fi + done + }; f + ''; }; }; From c761a6c6de8d94e2404b4b166196f35d5d1bbc37 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:45:49 +0200 Subject: [PATCH 19/23] refactor: :hammer: --- users/shared/programs/git.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/users/shared/programs/git.nix b/users/shared/programs/git.nix index c81a732..a26a0c8 100644 --- a/users/shared/programs/git.nix +++ b/users/shared/programs/git.nix @@ -22,6 +22,14 @@ pull.rebase = true; # Prune remote branches that have been deleted on the remote fetch.prune = true; + + # Produce cleaner diffs and include the common ancestor in conflict markers. + diff.algorithm = "histogram"; + diff.colorMoved = "default"; + merge.conflictStyle = "zdiff3"; + + # Reuse recorded conflict resolutions across rebases and merges. + rerere.enabled = true; # Delete only merged local branches whose upstream was removed. alias.cleanup-branches = '' !f() { From 5ae9cde2f664265b5ef929f7359e564fae6614bb Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 20:57:45 +0200 Subject: [PATCH 20/23] feat(nix): adopt nh for system management --- README.md | 26 +++++++++++++++++++-- justfile | 39 ++++++------------------------- users/shared/programs/default.nix | 1 + users/shared/programs/nh.nix | 14 +++++++++++ 4 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 users/shared/programs/nh.nix diff --git a/README.md b/README.md index c0b3802..99a9982 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,13 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ 1. Install Nix using the [Determinate Systems installer](https://install.determinate.systems/): ```shell - just nix-install + curl --proto '=https' --tlsv1.2 -sSf -L \ + https://install.determinate.systems/nix/tag/v0.32.2 | + sh -s -- install ``` + Restart the shell after installation. + 2. Clone the repository: ```shell @@ -41,11 +45,21 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ 3. Apply the configuration for your machine: ```shell - just nix-apply + sudo nix run nix-darwin -- switch \ + --flake .# \ + --show-trace ``` where `` is one of the systems defined in `flake.nix` (e.g. `macbook-pro-m4`). +4. Start a new shell, then install the asdf-managed tools: + + ```shell + ./users/shared/programs/asdf/install.sh + ``` + + The initial activation installs `nh` and the asdf runtime. Subsequent configuration changes use the Just recipes below. + ## Usage ```shell @@ -54,3 +68,11 @@ just nix-apply # apply configuration just nix-update-flake # update flake inputs just nix-gc # garbage collect unused packages ``` + +## Uninstall + +Remove the Determinate Nix installation explicitly: + +```shell +/nix/nix-installer uninstall +``` diff --git a/justfile b/justfile index 187a761..9ff30cf 100644 --- a/justfile +++ b/justfile @@ -2,51 +2,26 @@ default: @just --list --unsorted --list-heading $'Available commands:\n' -DS_NIX_INSTALLER_VERSION := "v0.32.2" - -[group('nix')] -[doc('Install Nix with Determinate Systems Nix Installer')] -nix-install: - @echo "Installing Nix using Determinate Systems installer ({{ DS_NIX_INSTALLER_VERSION }})." - @if command -v nix >/dev/null 2>&1; then \ - echo "Nix is already installed. Run 'nix --version' to check version."; \ - else \ - curl --proto '=https' --tlsv1.2 -sSf -L "https://install.determinate.systems/nix/tag/{{ DS_NIX_INSTALLER_VERSION }}" | sh -s -- install && \ - echo "Nix installation complete. Please restart your shell."; \ - fi - [group('nix')] [doc('Upgrade Nix')] nix-upgrade: @echo "Upgrading Nix." @sudo -i nix upgrade-nix -[group('nix')] -[doc('Uninstall Nix')] -nix-uninstall: - @echo "Uninstalling Nix." - @/nix/nix-installer uninstall - [group('nix')] [doc('Apply nix-darwin configuration for a given host')] nix-apply host: - @echo "Applying nix-darwin configuration for {{ host }}." - @sudo nix run nix-darwin -- switch --flake .#{{ host }} --show-trace - -[group('nix')] -[doc('Update the flake.lock file')] -nix-update-flake: - @nix flake update + @nh darwin switch . -H {{ host }} [group('nix')] -[doc('Collect Nix garbage (remove unused packages)')] -nix-gc: - @nix-collect-garbage +[doc('Update all or selected flake inputs')] +nix-update-flake *inputs='': + @nix flake update {{ inputs }} [group('nix')] -[doc('Delete all non-current Nix generations and collect garbage (removes rollback history)')] -nix-gc-delete-generations: - @sudo nix-collect-garbage -d +[doc('Clean old Nix generations while preserving recent rollback history')] +nix-clean: + @nh clean all --keep 5 --keep-since 7d [group('misc')] [doc('Initialize pre-commit hooks')] diff --git a/users/shared/programs/default.nix b/users/shared/programs/default.nix index 30ca854..7bce31f 100644 --- a/users/shared/programs/default.nix +++ b/users/shared/programs/default.nix @@ -16,6 +16,7 @@ ./git.nix ./krew.nix ./kubie.nix + ./nh.nix ./ssh.nix ./tmux.nix ./zoxide.nix diff --git a/users/shared/programs/nh.nix b/users/shared/programs/nh.nix new file mode 100644 index 0000000..d6a1edf --- /dev/null +++ b/users/shared/programs/nh.nix @@ -0,0 +1,14 @@ +{ + config, + pkgs, + ... +}: +{ + programs.nh = { + enable = true; + darwinFlake = + if pkgs.stdenv.hostPlatform.isDarwin then "${config.home.homeDirectory}/.config/dotfiles" else null; + }; + + home.packages = [ pkgs.nvd ]; +} From 174b0db49a95655467dc48d459d411f6e137c349 Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 21:03:06 +0200 Subject: [PATCH 21/23] fix(nix): remove invalid upgrade recipe --- justfile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/justfile b/justfile index 9ff30cf..90de22c 100644 --- a/justfile +++ b/justfile @@ -2,12 +2,6 @@ default: @just --list --unsorted --list-heading $'Available commands:\n' -[group('nix')] -[doc('Upgrade Nix')] -nix-upgrade: - @echo "Upgrading Nix." - @sudo -i nix upgrade-nix - [group('nix')] [doc('Apply nix-darwin configuration for a given host')] nix-apply host: From e264aa659339772f82dff23f9f515308b645a2da Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 21:05:13 +0200 Subject: [PATCH 22/23] docs(readme): update setup and usage workflow --- README.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 99a9982..4e55de4 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ ```shell curl --proto '=https' --tlsv1.2 -sSf -L \ - https://install.determinate.systems/nix/tag/v0.32.2 | + https://install.determinate.systems/nix | sh -s -- install ``` @@ -42,7 +42,7 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ cd ~/.config/dotfiles ``` -3. Apply the configuration for your machine: +3. Bootstrap the configuration for your machine: ```shell sudo nix run nix-darwin -- switch \ @@ -52,21 +52,32 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ where `` is one of the systems defined in `flake.nix` (e.g. `macbook-pro-m4`). -4. Start a new shell, then install the asdf-managed tools: +4. Start a new shell, then reconcile the asdf-managed tools: ```shell ./users/shared/programs/asdf/install.sh ``` - The initial activation installs `nh` and the asdf runtime. Subsequent configuration changes use the Just recipes below. +5. Install the repository hooks: + + ```shell + just pre-commit-init + ``` + +The initial activation installs `nh` and the asdf runtime. Subsequent configuration changes use the Just recipes below. ## Usage ```shell -just # list all available commands -just nix-apply # apply configuration -just nix-update-flake # update flake inputs -just nix-gc # garbage collect unused packages +just # list all available commands +just nix-apply # build, diff, and apply a host +just nix-update-flake # update all flake inputs +just nix-update-flake nixpkgs # update selected flake inputs +just nix-clean # clean old generations, keeping recent rollbacks +just asdf-sync # reconcile asdf plugins and versions +just pre-commit-init # install pre-commit and commit-msg hooks +just pre-commit-update # update hook revisions +just pre-commit-run # run all hooks ``` ## Uninstall From 2fb1b3f05452dcd394b770bdc23502633ef37e6c Mon Sep 17 00:00:00 2001 From: Gleb Khaykin Date: Sat, 8 Aug 2026 21:21:44 +0200 Subject: [PATCH 23/23] refactor(hosts): rename systems directory --- README.md | 2 +- flake.nix | 2 +- {systems => hosts}/macbook-pro-m1/default.nix | 0 {systems => hosts}/macbook-pro-m4-together/default.nix | 0 {systems => hosts}/macbook-pro-m4/default.nix | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename {systems => hosts}/macbook-pro-m1/default.nix (100%) rename {systems => hosts}/macbook-pro-m4-together/default.nix (100%) rename {systems => hosts}/macbook-pro-m4/default.nix (100%) diff --git a/README.md b/README.md index 4e55de4..dae7830 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Agent rules, skills, and MCP servers are defined once in `users/shared/programs/ | --------------- | ----------------------------------------------- | | `flake.nix` | Inputs and `darwinConfigurations` for each host | | `modules/` | System-level configuration and the package set | -| `systems/` | Per-host configuration, keyed by hostname | +| `hosts/` | Per-host configuration, keyed by hostname | | `users/shared/` | Program configuration shared across users | | `users//` | Per-user overrides | diff --git a/flake.nix b/flake.nix index 59b4149..23a9630 100644 --- a/flake.nix +++ b/flake.nix @@ -123,7 +123,7 @@ autoMigrate = true; }; } - ./systems/${name} + ./hosts/${name} ]; }; forAllSystems = nixpkgs.lib.genAttrs systems; diff --git a/systems/macbook-pro-m1/default.nix b/hosts/macbook-pro-m1/default.nix similarity index 100% rename from systems/macbook-pro-m1/default.nix rename to hosts/macbook-pro-m1/default.nix diff --git a/systems/macbook-pro-m4-together/default.nix b/hosts/macbook-pro-m4-together/default.nix similarity index 100% rename from systems/macbook-pro-m4-together/default.nix rename to hosts/macbook-pro-m4-together/default.nix diff --git a/systems/macbook-pro-m4/default.nix b/hosts/macbook-pro-m4/default.nix similarity index 100% rename from systems/macbook-pro-m4/default.nix rename to hosts/macbook-pro-m4/default.nix