This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

tools.claude: remove, with wrapperModules.claude-code and pkgs.levenshtein-merge

levenshtein-merge only merged the managed CLAUDE.md for the wrapper.
coaxmetal's hermes keeps the plain llm.claude-code CLI.

-736
-1
flake.nix
··· 210 210 ./skills/flake-module.nix 211 211 212 212 ./wrapperModules/pi/flake-module.nix 213 - ./wrapperModules/claude-code/flake-module.nix 214 213 ./wrapperModules/noctalia-shell/flake-module.nix 215 214 { 216 215 options.flake.wrapperModules = nixpkgs.lib.mkOption {
-157
pkgs/levenshtein-merge/levenshtein.c
··· 1 - #include <stdio.h> 2 - #include <stdlib.h> 3 - #include <string.h> 4 - 5 - static int min3(int a, int b, int c) { 6 - if (a < b) return a < c ? a : c; 7 - return b < c ? b : c; 8 - } 9 - 10 - static int distance(const char *a, const char *b) { 11 - size_t la = strlen(a), lb = strlen(b); 12 - int *prev = calloc(lb + 1, sizeof(int)); 13 - if (!prev) exit(1); 14 - for (size_t j = 0; j <= lb; j++) prev[j] = (int)j; 15 - for (size_t i = 1; i <= la; i++) { 16 - int corner = prev[0]; 17 - prev[0] = (int)i; 18 - for (size_t j = 1; j <= lb; j++) { 19 - int val = min3(prev[j] + 1, prev[j-1] + 1, 20 - corner + (a[i-1] != b[j-1])); 21 - corner = prev[j]; 22 - prev[j] = val; 23 - } 24 - } 25 - int res = prev[lb]; 26 - free(prev); 27 - return res; 28 - } 29 - 30 - /* Collapse whitespace and trim. Returns malloc'd string. */ 31 - static char *normalize(const char *s) { 32 - while (*s == ' ' || *s == '\t') s++; 33 - size_t len = strlen(s); 34 - char *out = malloc(len + 1); 35 - if (!out) exit(1); 36 - size_t o = 0; 37 - int in_space = 0; 38 - for (size_t i = 0; i < len; i++) { 39 - if (s[i] == ' ' || s[i] == '\t') { 40 - if (!in_space && o > 0) out[o++] = ' '; 41 - in_space = 1; 42 - } else { 43 - out[o++] = s[i]; 44 - in_space = 0; 45 - } 46 - } 47 - if (o > 0 && out[o-1] == ' ') o--; 48 - out[o] = '\0'; 49 - return out; 50 - } 51 - 52 - static int fuzzy_match(const char *a, const char *b, double threshold) { 53 - int dist = distance(a, b); 54 - size_t la = strlen(a), lb = strlen(b); 55 - size_t m = la > lb ? la : lb; 56 - if (m == 0) return 1; 57 - return (double)dist / (double)m <= threshold; 58 - } 59 - 60 - /* Dynamic line array */ 61 - struct lines { char **l; size_t n, cap; }; 62 - 63 - static void lines_init(struct lines *v) { 64 - v->n = 0; v->cap = 64; 65 - v->l = malloc(v->cap * sizeof(char *)); 66 - if (!v->l) exit(1); 67 - } 68 - 69 - static void lines_push(struct lines *v, char *s) { 70 - if (v->n == v->cap) { 71 - v->cap *= 2; 72 - v->l = realloc(v->l, v->cap * sizeof(char *)); 73 - if (!v->l) exit(1); 74 - } 75 - v->l[v->n++] = s; 76 - } 77 - 78 - static void read_lines(const char *path, struct lines *v) { 79 - FILE *f = fopen(path, "r"); 80 - if (!f) return; 81 - char *line = NULL; 82 - size_t cap = 0; 83 - ssize_t len; 84 - while ((len = getline(&line, &cap, f)) != -1) { 85 - if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; 86 - lines_push(v, strdup(line)); 87 - } 88 - free(line); 89 - fclose(f); 90 - } 91 - 92 - int main(int argc, char **argv) { 93 - if (argc == 3) { 94 - printf("%d\n", distance(argv[1], argv[2])); 95 - return 0; 96 - } 97 - if (argc == 5 && strcmp(argv[1], "-t") == 0) { 98 - double t = atof(argv[2]); 99 - return fuzzy_match(argv[3], argv[4], t) ? 0 : 1; 100 - } 101 - if (argc == 5 && strcmp(argv[1], "merge") == 0) { 102 - double threshold = atof(argv[4]); 103 - 104 - struct lines target, managed, known; 105 - lines_init(&target); 106 - lines_init(&managed); 107 - lines_init(&known); 108 - 109 - read_lines(argv[3], &target); 110 - read_lines(argv[2], &managed); 111 - 112 - /* Build normalized index of target lines */ 113 - for (size_t i = 0; i < target.n; i++) { 114 - char *n = normalize(target.l[i]); 115 - if (n[0]) lines_push(&known, n); 116 - else free(n); 117 - } 118 - 119 - /* Find managed lines missing from target */ 120 - struct lines missing; 121 - lines_init(&missing); 122 - 123 - for (size_t i = 0; i < managed.n; i++) { 124 - char *n = normalize(managed.l[i]); 125 - if (!n[0]) { free(n); continue; } 126 - int found = 0; 127 - for (size_t j = 0; j < known.n; j++) { 128 - if (fuzzy_match(n, known.l[j], threshold)) { 129 - found = 1; 130 - break; 131 - } 132 - } 133 - if (!found) { 134 - lines_push(&missing, managed.l[i]); 135 - lines_push(&known, n); 136 - } else { 137 - free(n); 138 - } 139 - } 140 - 141 - if (missing.n == 0) return 0; 142 - 143 - FILE *f = fopen(argv[3], "a"); 144 - if (!f) { perror(argv[3]); return 1; } 145 - fprintf(f, "\n"); 146 - for (size_t i = 0; i < missing.n; i++) 147 - fprintf(f, "%s\n", missing.l[i]); 148 - fclose(f); 149 - return 0; 150 - } 151 - 152 - fprintf(stderr, 153 - "usage: levenshtein-merge <a> <b>\n" 154 - " levenshtein-merge -t <threshold> <a> <b>\n" 155 - " levenshtein-merge merge <managed> <target> <threshold>\n"); 156 - return 2; 157 - }
-25
pkgs/levenshtein-merge/package.nix
··· 1 - { 2 - lib, 3 - stdenv, 4 - }: 5 - stdenv.mkDerivation { 6 - pname = "levenshtein-merge"; 7 - version = "0.1.0"; 8 - 9 - src = ./.; 10 - 11 - buildPhase = '' 12 - $CC -O2 -o levenshtein-merge levenshtein.c 13 - ''; 14 - 15 - installPhase = '' 16 - install -Dm755 levenshtein-merge $out/bin/levenshtein-merge 17 - ''; 18 - 19 - meta = { 20 - description = "Fuzzy line-level file merge using Levenshtein distance"; 21 - license = lib.licenses.mit; 22 - mainProgram = "levenshtein-merge"; 23 - platforms = lib.platforms.all; 24 - }; 25 - }
-29
tools/claude/CLAUDE.md
··· 1 - # CLAUDE.md 2 - 3 - Global guidance for Claude Code across all projects. 4 - 5 - ## Instructions 6 - 7 - - Format code with the project's formatter before committing (e.g. `nix fmt`, `ruff format`, `prettier`). 8 - - Write shell scripts that pass ShellCheck. 9 - - Write Python code for 3.12 that conforms to `ruff format` and passes `ruff check`. 10 - - Always test/lint/format your code before committing. 11 - - Add debug output or unit tests when troubleshooting. 12 - - Avoid mocking in tests. 13 - - Use the `gh` tool to interact with GitHub. 14 - - Do not include "claude" in commit messages. 15 - - Take extra care not to commit lines with trailing whitespace. 16 - - Use the `tmux` skill for long-running tasks. Use a session with a `claude-` prefix — create one if no idle session exists. 17 - - Use the `notify` skill (`s say "..."`) to get the user's attention when a long task finishes, you need input to continue, or something needs human review. 18 - - Use git-mcp for advanced git operations (interactive staging, stash management, bisecting). 19 - 20 - ## Nix 21 - 22 - When working in a Nix flake: 23 - 24 - - Use `--log-format bar-with-logs` with `nix build` for improved build log output. 25 - - Add new untracked files with `git add` — flake eval only sees tracked files. 26 - - Use `nix-locate` to find packages by file path. 27 - - Use `nix run nixpkgs#foo` to execute tools that aren't installed. 28 - - Use `nix eval` instead of `nix flake show` to look up attributes in a flake. 29 - For listing: `nix eval .#packages.<system> --apply builtins.attrNames --json | jq`.
-153
tools/claude/flake-module.nix
··· 1 - { self, inputs, ... }: 2 - { 3 - perSystem = 4 - { pkgs, system, ... }: 5 - let 6 - claude = inputs.wrappers.lib.wrapModule { 7 - imports = [ self.wrapperModules.claude-code ]; 8 - inherit pkgs; 9 - 10 - package = self.lib.halalify self.legacyPackages.${system}.llm.claude-code; 11 - 12 - claudeMd = ./CLAUDE.md; 13 - 14 - mcpServers = { 15 - git-mcp = { 16 - command = "${self.packages.${system}.git-mcp}/bin/git-mcp"; 17 - }; 18 - }; 19 - 20 - skills = { 21 - hedgedoc = "${self.legacyPackages.${system}.skills.hedgedoc}/share/hedgedoc"; 22 - notify = "${self.legacyPackages.${system}.skills.notify}/share/notify"; 23 - reverse-engineering = "${ 24 - self.legacyPackages.${system}.skills.reverse-engineering 25 - }/share/reverse-engineering"; 26 - tmux = "${self.legacyPackages.${system}.skills.tmux}/share/tmux"; 27 - websearch = "${self.legacyPackages.${system}.skills.websearch}/share/websearch"; 28 - }; 29 - 30 - permissions = { 31 - defaultMode = "auto"; 32 - allow = [ 33 - # nix operations 34 - "Bash(nix build:*)" 35 - "Bash(nix eval:*)" 36 - "Bash(nix fmt:*)" 37 - "Bash(nix flake:*)" 38 - "Bash(nix develop:*)" 39 - "Bash(nix run:*)" 40 - "Bash(nix search:*)" 41 - "Bash(nix log:*)" 42 - "Bash(nix path-info:*)" 43 - "Bash(nix store:*)" 44 - "Bash(nix derivation:*)" 45 - "Bash(nix hash:*)" 46 - "Bash(nix-build:*)" 47 - "Bash(nix-instantiate:*)" 48 - "Bash(nix-locate:*)" 49 - "Bash(nix-prefetch-url:*)" 50 - "Bash(nixos-rebuild:*)" 51 - "Bash(darwin-rebuild:*)" 52 - # clan 53 - "Bash(clan:*)" 54 - # git read operations 55 - "Bash(git status:*)" 56 - "Bash(git diff:*)" 57 - "Bash(git log:*)" 58 - "Bash(git show:*)" 59 - "Bash(git branch:*)" 60 - "Bash(git remote:*)" 61 - "Bash(git stash list:*)" 62 - "Bash(git rev-parse:*)" 63 - "Bash(git describe:*)" 64 - "Bash(git tag:*)" 65 - "Bash(git ls-files:*)" 66 - "Bash(git ls-remote:*)" 67 - # git write operations 68 - "Bash(git add:*)" 69 - "Bash(git commit:*)" 70 - "Bash(git checkout:*)" 71 - "Bash(git switch:*)" 72 - "Bash(git merge:*)" 73 - "Bash(git rebase:*)" 74 - "Bash(git stash:*)" 75 - "Bash(git cherry-pick:*)" 76 - "Bash(git fetch:*)" 77 - "Bash(git pull:*)" 78 - # common read-only tools 79 - "Bash(cat:*)" 80 - "Bash(ls:*)" 81 - "Bash(find:*)" 82 - "Bash(grep:*)" 83 - "Bash(rg:*)" 84 - "Bash(fd:*)" 85 - "Bash(head:*)" 86 - "Bash(tail:*)" 87 - "Bash(wc:*)" 88 - "Bash(sort:*)" 89 - "Bash(uniq:*)" 90 - "Bash(jq:*)" 91 - "Bash(tree:*)" 92 - "Bash(file:*)" 93 - "Bash(which:*)" 94 - "Bash(realpath:*)" 95 - "Bash(readlink:*)" 96 - "Bash(dirname:*)" 97 - "Bash(basename:*)" 98 - "Bash(stat:*)" 99 - "Bash(du:*)" 100 - "Bash(df:*)" 101 - "Bash(env:*)" 102 - "Bash(echo:*)" 103 - "Bash(printf:*)" 104 - "Bash(test:*)" 105 - "Bash(tr:*)" 106 - "Bash(sed:*)" 107 - "Bash(awk:*)" 108 - "Bash(cut:*)" 109 - "Bash(diff:*)" 110 - "Bash(tee:*)" 111 - "Bash(xargs:*)" 112 - "Bash(mkdir:*)" 113 - "Bash(cp:*)" 114 - "Bash(mv:*)" 115 - "Bash(touch:*)" 116 - "Bash(chmod:*)" 117 - "Bash(ln:*)" 118 - # dev tools 119 - "Bash(gh:*)" 120 - "Bash(shellcheck:*)" 121 - "Bash(ruff:*)" 122 - "Bash(kagi-search:*)" 123 - "Bash(curl:*)" 124 - "Bash(tmux:*)" 125 - "Bash(s:*)" 126 - # file edits 127 - "Edit" 128 - "Write" 129 - "Read" 130 - ]; 131 - }; 132 - 133 - extraPackages = [ 134 - self.packages.${system}.kagi-search 135 - pkgs.curl 136 - pkgs.jq 137 - pkgs.python3 138 - pkgs.ripgrep 139 - pkgs.fd 140 - pkgs.tmux 141 - pkgs.tree 142 - ]; 143 - 144 - settings = { 145 - # Prefer compact output for better context usage 146 - verbose = true; 147 - }; 148 - }; 149 - in 150 - { 151 - packages.claude = claude.wrapper; 152 - }; 153 - }
-371
wrapperModules/claude-code/flake-module.nix
··· 1 - { self, ... }: 2 - { 3 - flake.wrapperModules.claude-code = 4 - { 5 - config, 6 - lib, 7 - ... 8 - }: 9 - let 10 - pkgs = config.pkgs; 11 - jsonFormat = pkgs.formats.json { }; 12 - 13 - # Assemble a single Claude Code plugin directory containing skills, 14 - # commands, and agents. Claude Code discovers a directory as a plugin 15 - # when it contains `.claude-plugin/plugin.json`. 16 - pluginDir = 17 - pkgs.runCommand "claude-plugin-${config.pluginName}" 18 - { 19 - passthru = { inherit (config) pluginManifest; }; 20 - } 21 - '' 22 - mkdir -p $out/.claude-plugin 23 - cp ${jsonFormat.generate "plugin.json" config.pluginManifest} \ 24 - $out/.claude-plugin/plugin.json 25 - 26 - ${lib.optionalString (config.skills != { }) '' 27 - mkdir -p $out/skills 28 - ${lib.concatStringsSep "\n" ( 29 - lib.mapAttrsToList (name: src: "ln -s ${src} $out/skills/${name}") config.skills 30 - )} 31 - ''} 32 - 33 - ${lib.optionalString (config.commands != { }) '' 34 - mkdir -p $out/commands 35 - ${lib.concatStringsSep "\n" ( 36 - lib.mapAttrsToList (name: src: "ln -s ${src} $out/commands/${name}") config.commands 37 - )} 38 - ''} 39 - 40 - ${lib.optionalString (config.agents != { }) '' 41 - mkdir -p $out/agents 42 - ${lib.concatStringsSep "\n" ( 43 - lib.mapAttrsToList (name: src: "ln -s ${src} $out/agents/${name}") config.agents 44 - )} 45 - ''} 46 - 47 - ${lib.optionalString (config.pluginExtraFiles != { }) '' 48 - ${lib.concatStringsSep "\n" ( 49 - lib.mapAttrsToList (path: src: '' 50 - mkdir -p "$out/$(dirname ${path})" 51 - ln -s ${src} "$out/${path}" 52 - '') config.pluginExtraFiles 53 - )} 54 - ''} 55 - ''; 56 - 57 - # Compose a settings.json from the typed options + freeform overrides. 58 - # Order matters: typed options take precedence over freeform `settings` 59 - # for the keys they own, but freeform can still set anything else. 60 - mergedSettings = lib.foldl' lib.recursiveUpdate config.settings ( 61 - lib.optional (config.mcpServers != { }) { mcpServers = config.mcpServers; } 62 - ++ lib.optional (config.hooks != { }) { hooks = config.hooks; } 63 - ++ lib.optional (config.permissions != { }) { permissions = config.permissions; } 64 - ); 65 - 66 - settingsFile = jsonFormat.generate "claude-settings.json" mergedSettings; 67 - 68 - levenshtein = self.packages.${pkgs.system}.levenshtein-merge; 69 - in 70 - { 71 - _class = "wrapper"; 72 - 73 - options = { 74 - pluginName = lib.mkOption { 75 - type = lib.types.str; 76 - default = "superconfig"; 77 - description = "Name of the bundled plugin directory."; 78 - }; 79 - 80 - pluginManifest = lib.mkOption { 81 - type = jsonFormat.type; 82 - default = { 83 - name = config.pluginName; 84 - version = "0.0.0"; 85 - description = "Nix-managed Claude Code plugin bundle."; 86 - }; 87 - defaultText = lib.literalExpression '' 88 - { name = config.pluginName; version = "0.0.0"; description = "..."; } 89 - ''; 90 - description = "Contents of `.claude-plugin/plugin.json` for the bundled plugin."; 91 - }; 92 - 93 - skills = lib.mkOption { 94 - type = lib.types.attrsOf lib.types.path; 95 - default = { }; 96 - example = lib.literalExpression '' 97 - { 98 - websearch = ./skills/websearch; 99 - } 100 - ''; 101 - description = '' 102 - Skills to bundle. Each entry is a directory containing `SKILL.md` 103 - and any helper scripts/resources. Symlinked into 104 - `<plugin>/skills/<name>` and discovered by Claude Code automatically. 105 - ''; 106 - }; 107 - 108 - commands = lib.mkOption { 109 - type = lib.types.attrsOf lib.types.path; 110 - default = { }; 111 - description = '' 112 - Slash commands. Each entry is a markdown file (or directory) 113 - symlinked into `<plugin>/commands/<name>`. 114 - ''; 115 - }; 116 - 117 - agents = lib.mkOption { 118 - type = lib.types.attrsOf lib.types.path; 119 - default = { }; 120 - description = '' 121 - Sub-agents. Each entry is a markdown file (or directory) symlinked 122 - into `<plugin>/agents/<name>`. 123 - ''; 124 - }; 125 - 126 - pluginExtraFiles = lib.mkOption { 127 - type = lib.types.attrsOf lib.types.path; 128 - default = { }; 129 - example = lib.literalExpression '' 130 - { 131 - "hooks/format.sh" = ./hooks/format.sh; 132 - } 133 - ''; 134 - description = '' 135 - Additional files to drop into the plugin directory. Keys are 136 - relative paths inside the plugin directory; values are paths to 137 - symlink in. Use this for hook scripts or anything else not covered 138 - by the typed options above. 139 - ''; 140 - }; 141 - 142 - plugins = lib.mkOption { 143 - type = lib.types.listOf lib.types.path; 144 - default = [ ]; 145 - example = lib.literalExpression '' 146 - [ 147 - # A fetched plugin from GitHub: 148 - (pkgs.fetchFromGitHub { 149 - owner = "Myr-Aya"; 150 - repo = "GouvernAI-claude-code-plugin"; 151 - rev = "main"; 152 - hash = "sha256-AAAA..."; 153 - # The plugin directory is a subdirectory of the repo: 154 - } + "/gouvernai") 155 - 156 - # A local plugin directory (must contain .claude-plugin/plugin.json): 157 - ./my-local-plugin 158 - 159 - # A plugin built by a Nix derivation: 160 - my-plugin-package 161 - ] 162 - ''; 163 - description = '' 164 - Additional Claude Code plugin directories to load. Each entry 165 - must be a path to a directory containing `.claude-plugin/plugin.json`. 166 - Passed as extra `--plugin-dir` flags alongside the bundled plugin. 167 - 168 - Plugins can provide skills, commands, agents, and hooks. Use this 169 - to declaratively install third-party plugins (e.g. from GitHub) 170 - or locally developed ones without going through the Claude Code 171 - marketplace. 172 - ''; 173 - }; 174 - 175 - mcpServers = lib.mkOption { 176 - type = jsonFormat.type; 177 - default = { }; 178 - example = lib.literalExpression '' 179 - { 180 - git-mcp = { 181 - command = "''${pkgs.git-mcp}/bin/git-mcp"; 182 - }; 183 - tmux-mcp = { 184 - type = "stdio"; 185 - command = "''${pkgs.tmux-mcp}/bin/tmux-mcp"; 186 - args = [ ]; 187 - env = { }; 188 - }; 189 - } 190 - ''; 191 - description = '' 192 - MCP servers to register. Merged into `settings.json` under 193 - `mcpServers`. Each entry follows Claude Code's MCP server schema 194 - (stdio: `{ command, args, env }`; http/sse: `{ type, url, headers }`). 195 - ''; 196 - }; 197 - 198 - hooks = lib.mkOption { 199 - type = jsonFormat.type; 200 - default = { }; 201 - example = lib.literalExpression '' 202 - { 203 - PreToolUse = [ 204 - { 205 - matcher = "*"; 206 - hooks = [ 207 - { 208 - type = "command"; 209 - command = "''${pkgs.my-permission-bridge}/bin/permission-forward"; 210 - } 211 - ]; 212 - } 213 - ]; 214 - } 215 - ''; 216 - description = '' 217 - Hook definitions. Shape matches Claude Code's `settings.json` hooks 218 - field. Supported events include `PreToolUse`, `PostToolUse`, 219 - `UserPromptSubmit`, `Stop`, `SessionStart`, `Notification`, and 220 - `PreCompact`. 221 - ''; 222 - }; 223 - 224 - permissions = lib.mkOption { 225 - type = jsonFormat.type; 226 - default = { }; 227 - example = lib.literalExpression '' 228 - { 229 - defaultMode = "acceptEdits"; 230 - allow = [ 231 - "Bash(nix build:*)" 232 - "Bash(nix eval:*)" 233 - "Bash(nix fmt:*)" 234 - "Bash(git status:*)" 235 - "Bash(git diff:*)" 236 - ]; 237 - deny = [ ]; 238 - ask = [ ]; 239 - } 240 - ''; 241 - description = '' 242 - Permission rules. Shape matches Claude Code's `settings.json` 243 - `permissions` field. Use `allow`/`deny`/`ask` pattern lists to 244 - short-circuit the permission flow before any hook fires. 245 - ''; 246 - }; 247 - 248 - settings = lib.mkOption { 249 - type = jsonFormat.type; 250 - default = { }; 251 - description = '' 252 - Freeform additional `settings.json` content. Merged with the 253 - typed options above (which take precedence on conflict). Use this 254 - for anything not covered by a dedicated option. 255 - ''; 256 - }; 257 - 258 - claudeMd = lib.mkOption { 259 - type = lib.types.nullOr lib.types.path; 260 - default = null; 261 - description = '' 262 - Optional `CLAUDE.md` whose lines are fuzzy-merged into 263 - `$CLAUDE_MD_DIR/CLAUDE.md` (default `~/.claude/CLAUDE.md`) on 264 - every invocation. Only adds lines, never removes user content. 265 - Each non-blank managed line is skipped when a line within 266 - `claudeMdMergeThreshold` (Levenshtein ratio) already exists in 267 - the target file. 268 - ''; 269 - }; 270 - 271 - projectStateWipeKeys = lib.mkOption { 272 - type = lib.types.listOf lib.types.str; 273 - default = [ "mcpServers" ]; 274 - description = '' 275 - Keys to delete from every `projects.<dir>` entry of 276 - `~/.claude.json` on each invocation. Use this to purge 277 - unmanaged runtime drift that the wrapper otherwise can't 278 - override — most notably stale `mcpServers` registrations 279 - created by `claude mcp add`, which pin GC-able `/nix/store` 280 - paths and shadow the canonical set the wrapper writes via 281 - `--settings`. 282 - 283 - Anything not listed here is left alone, so new fields 284 - claude-code may add in future versions are preserved by 285 - default. Set to `[ ]` to disable the cleanup step. 286 - ''; 287 - }; 288 - 289 - claudeMdMergeThreshold = lib.mkOption { 290 - type = lib.types.float; 291 - default = 0.15; 292 - description = '' 293 - Levenshtein distance ratio (distance / max-length) below which 294 - two normalized lines are considered the same during 295 - `claudeMd` merging. 0.0 requires exact match; higher values 296 - tolerate more typos. Default 0.15 allows roughly one typo per 297 - seven characters. 298 - ''; 299 - }; 300 - 301 - pluginDir = lib.mkOption { 302 - type = lib.types.package; 303 - readOnly = true; 304 - default = pluginDir; 305 - defaultText = "<assembled plugin directory>"; 306 - description = "The assembled plugin directory passed via `--plugin-dir`."; 307 - }; 308 - 309 - settingsFile = lib.mkOption { 310 - type = lib.types.package; 311 - readOnly = true; 312 - default = settingsFile; 313 - defaultText = "<assembled settings.json>"; 314 - description = "The assembled settings.json passed via `--settings`."; 315 - }; 316 - }; 317 - 318 - config.package = lib.mkDefault (self.lib.halalify pkgs.claude-code); 319 - 320 - # Always inject our plugin dir + settings file into every invocation. 321 - # `--plugin-dir` is repeatable so users can still pass more on the CLI. 322 - config.flags."--plugin-dir" = { 323 - value = [ "${pluginDir}" ] ++ map toString config.plugins; 324 - order = 100; 325 - }; 326 - config.flags."--settings" = { 327 - value = "${settingsFile}"; 328 - order = 101; 329 - }; 330 - 331 - config.preHook = 332 - lib.optionalString (config.claudeMd != null) '' 333 - CLAUDE_MD_DIR="''${CLAUDE_MD_DIR:-$HOME/.claude}" 334 - mkdir -p "$CLAUDE_MD_DIR" 335 - touch "$CLAUDE_MD_DIR/CLAUDE.md" 336 - ${levenshtein}/bin/levenshtein-merge merge \ 337 - ${config.claudeMd} \ 338 - "$CLAUDE_MD_DIR/CLAUDE.md" \ 339 - ${toString config.claudeMdMergeThreshold} \ 340 - || echo "levenshtein merge failed; leaving CLAUDE.md untouched" >&2 341 - '' 342 - + lib.optionalString (config.projectStateWipeKeys != [ ]) '' 343 - CLAUDE_JSON="$HOME/.claude.json" 344 - if [ -f "$CLAUDE_JSON" ]; then 345 - CLAUDE_JSON_TMP=$(mktemp "$CLAUDE_JSON.XXXXXX") 346 - if ${pkgs.jq}/bin/jq \ 347 - --argjson wipe ${lib.escapeShellArg (builtins.toJSON config.projectStateWipeKeys)} ' 348 - if .projects then 349 - .projects |= with_entries( 350 - .value |= reduce $wipe[] as $k (.; del(.[$k])) 351 - ) 352 - else . end 353 - ' "$CLAUDE_JSON" > "$CLAUDE_JSON_TMP"; then 354 - mv "$CLAUDE_JSON_TMP" "$CLAUDE_JSON" 355 - else 356 - rm -f "$CLAUDE_JSON_TMP" 357 - echo "claude.json projects-state cleanup failed; leaving file untouched" >&2 358 - fi 359 - fi 360 - ''; 361 - 362 - config.meta.maintainers = [ 363 - { 364 - name = "lassulus"; 365 - github = "lassulus"; 366 - githubId = 621375; 367 - } 368 - ]; 369 - config.meta.platforms = lib.platforms.all; 370 - }; 371 - }