| 1 | { |
| 2 | config, |
| 3 | lib, |
| 4 | pkgs, |
| 5 | ... |
| 6 | }: |
| 7 | let |
| 8 | agentsFile = "${config.home.homeDirectory}/config/users/clover/agents/AGENTS.md"; |
| 9 | simpleBrowserBridge = "${config.home.homeDirectory}/config/users/clover/packages/open-in-vscode-browser/extension"; |
| 10 | claudeStatusline = "${config.home.homeDirectory}/config/users/clover/agents/statusline.sh"; |
| 11 | claudeAgents = "${config.home.homeDirectory}/config/users/clover/agents/subagents"; |
| 12 | codexAgents = "${config.home.homeDirectory}/config/users/clover/agents/codex-agents"; |
| 13 | cloverSkills = "${config.home.homeDirectory}/config/users/clover/agents/skills"; |
| 14 | codexUpsert = "${config.home.homeDirectory}/config/users/clover/scripts/codex-config-upsert.py"; |
| 15 | codexConfig = "${config.home.homeDirectory}/config/users/clover/codex-config.toml"; |
| 16 | codexPython = pkgs.python3.withPackages (ps: [ ps.tomlkit ]); |
| 17 | in |
| 18 | { |
| 19 | imports = [ |
| 20 | ./zsh |
| 21 | ./jujutsu.nix |
| 22 | ]; |
| 23 | |
| 24 | home = { |
| 25 | stateVersion = "23.05"; # Please read the comment before changing. |
| 26 | packages = with pkgs; [ |
| 27 | ffmpeg-full |
| 28 | ripgrep |
| 29 | uv |
| 30 | nh |
| 31 | autofmt |
| 32 | gnupg |
| 33 | just |
| 34 | tmux |
| 35 | pnpm |
| 36 | nodejs_26 |
| 37 | (callPackage ./packages/agent-linear { }) |
| 38 | # `open` shim that routes URLs into the VS Code Simple Browser; see |
| 39 | # the home.file symlink for the companion extension. |
| 40 | (callPackage ./packages/open-in-vscode-browser { }) |
| 41 | ]; |
| 42 | # nix node's default global prefix is its read-only store path. |
| 43 | sessionVariables.NPM_CONFIG_PREFIX = "$HOME/.local"; |
| 44 | sessionPath = [ "$HOME/.local/share/pnpm/bin" ]; |
| 45 | }; |
| 46 | |
| 47 | xdg.configFile."opencode/AGENTS.md".source = config.lib.file.mkOutOfStoreSymlink agentsFile; |
| 48 | |
| 49 | # claude code's global instructions live at ~/.claude/CLAUDE.md (the analog |
| 50 | # of opencode's global AGENTS.md); point it at the same file. |
| 51 | home.file.".claude/CLAUDE.md".source = config.lib.file.mkOutOfStoreSymlink agentsFile; |
| 52 | |
| 53 | # Subagent definitions the global CLAUDE.md delegation section routes to; |
| 54 | # the built-in agents are denied in ~/.claude/settings.json so these are the |
| 55 | # only ones reachable. |
| 56 | home.file.".claude/agents".source = config.lib.file.mkOutOfStoreSymlink claudeAgents; |
| 57 | |
| 58 | # codex reads its global instructions from $CODEX_HOME/AGENTS.md. |
| 59 | home.file.".codex/AGENTS.md".source = config.lib.file.mkOutOfStoreSymlink agentsFile; |
| 60 | |
| 61 | home.file.".codex/agents".source = config.lib.file.mkOutOfStoreSymlink codexAgents; |
| 62 | |
| 63 | # One SKILL.md in the shared format; both harnesses read it from their own |
| 64 | # personal skills directory, linked per-skill to leave unmanaged ones alone. |
| 65 | home.file.".claude/skills/ui-copy".source = |
| 66 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-copy"; |
| 67 | home.file.".codex/skills/ui-copy".source = |
| 68 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-copy"; |
| 69 | home.file.".claude/skills/ui-prototype".source = |
| 70 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-prototype"; |
| 71 | home.file.".codex/skills/ui-prototype".source = |
| 72 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-prototype"; |
| 73 | home.file.".claude/skills/ux-flows".source = |
| 74 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ux-flows"; |
| 75 | home.file.".codex/skills/ux-flows".source = |
| 76 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ux-flows"; |
| 77 | home.file.".claude/skills/ui-craft".source = |
| 78 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-craft"; |
| 79 | home.file.".codex/skills/ui-craft".source = |
| 80 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-craft"; |
| 81 | home.file.".claude/skills/ux-testing".source = |
| 82 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ux-testing"; |
| 83 | home.file.".codex/skills/ux-testing".source = |
| 84 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ux-testing"; |
| 85 | home.file.".claude/skills/ui-review-loop".source = |
| 86 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-review-loop"; |
| 87 | home.file.".codex/skills/ui-review-loop".source = |
| 88 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/ui-review-loop"; |
| 89 | home.file.".claude/skills/maintain-it".source = |
| 90 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/maintain-it"; |
| 91 | home.file.".codex/skills/maintain-it".source = |
| 92 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/maintain-it"; |
| 93 | home.file.".claude/skills/accessibility".source = |
| 94 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/accessibility"; |
| 95 | home.file.".codex/skills/accessibility".source = |
| 96 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/accessibility"; |
| 97 | home.file.".codex/skills/shale-issues".source = |
| 98 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/shale-issues"; |
| 99 | home.file.".claude/skills/openai-docs".source = |
| 100 | config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/.codex/skills/.system/openai-docs"; |
| 101 | home.file.".codex/skills/update-config".source = |
| 102 | config.lib.file.mkOutOfStoreSymlink "${cloverSkills}/update-config"; |
| 103 | |
| 104 | # codex rewrites ~/.codex/config.toml at runtime (project trust, plugins, |
| 105 | # skills), so the managed keys are upserted into it instead of symlinked. |
| 106 | home.activation.codexConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] '' |
| 107 | if [ -d "${config.home.homeDirectory}/.codex" ]; then |
| 108 | run ${codexPython}/bin/python3 ${codexUpsert} ${codexConfig} \ |
| 109 | "${config.home.homeDirectory}/.codex/config.toml" |
| 110 | fi |
| 111 | ''; |
| 112 | |
| 113 | # Referenced by the `statusLine.command` entry in ~/.claude/settings.json, |
| 114 | # which claude code rewrites itself and so stays unmanaged. |
| 115 | home.file.".claude/statusline-command.sh".source = |
| 116 | config.lib.file.mkOutOfStoreSymlink claudeStatusline; |
| 117 | |
| 118 | home.file.".tmux.conf".text = '' |
| 119 | set -g focus-events on |
| 120 | ''; |
| 121 | |
| 122 | # Companion extension for the `open-in-vscode-browser` shim. Symlinked |
| 123 | # (rather than copied) so edits to the source are live after a window reload. |
| 124 | home.file.".vscode/extensions/clover.simple-browser-bridge".source = |
| 125 | config.lib.file.mkOutOfStoreSymlink simpleBrowserBridge; |
| 126 | |
| 127 | programs = { |
| 128 | # sort-lines:start |
| 129 | # bat.enable = true; |
| 130 | btop.enable = true; |
| 131 | fd.enable = true; |
| 132 | hyfetch.enable = true; |
| 133 | # sort-lines:end |
| 134 | |
| 135 | ghostty = { |
| 136 | enable = true; |
| 137 | shader = "cursor-smear-black.glsl"; |
| 138 | package = null; |
| 139 | settings = { |
| 140 | theme = "light:Farmhouse Light,dark:Challenger Deep"; |
| 141 | font-family = "AT Name Mono"; |
| 142 | adjust-cursor-thickness = 2; |
| 143 | background-opacity = 0.95; |
| 144 | background-blur = true; |
| 145 | unfocused-split-opacity = 1; |
| 146 | }; |
| 147 | }; |
| 148 | |
| 149 | git = { |
| 150 | ignores = [ "AGENTS.md" ]; |
| 151 | # use a git-specific email |
| 152 | settings.user.email = "git@paperclover.net"; |
| 153 | settings.gpg.ssh.allowedSignersFile = "${pkgs.writeText "allowed_signers" '' |
| 154 | git@paperclover.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAVa1aq1LEj/VXHJYlR5HJpK65lS/9jOL/dAB9ZS1chd |
| 155 | ''}"; |
| 156 | # signing |
| 157 | signing = { |
| 158 | signByDefault = true; |
| 159 | key = "~/.ssh/id_ed25519"; |
| 160 | format = "ssh"; |
| 161 | }; |
| 162 | }; |
| 163 | |
| 164 | ssh = { |
| 165 | enable = true; |
| 166 | enableDefaultConfig = false; |
| 167 | extraConfig = '' |
| 168 | SetEnv TERM=xterm-256color |
| 169 | ''; |
| 170 | # extraConfig asserts a default host block exists |
| 171 | settings."*" = { |
| 172 | AddKeysToAgent = "yes"; |
| 173 | } // lib.optionalAttrs pkgs.stdenv.isDarwin { |
| 174 | UseKeychain = true; |
| 175 | }; |
| 176 | }; |
| 177 | }; |
| 178 | } |