This repository has no description
0

Configure Feed

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

wrapperModules.pi: add pi wrapper module with declarative settings

+203 -75
+1
flake.nix
··· 185 185 ./skills/flake-module.nix 186 186 ./games/flake-module.nix 187 187 ./machines/pixel8/flake-module.nix 188 + ./wrapperModules/pi/flake-module.nix 188 189 ] 189 190 ++ ( 190 191 # Auto-import all flake-module.nix files from tools subdirectories
+38 -75
tools/pi/flake-module.nix
··· 1 1 { self, inputs, ... }: 2 2 { 3 3 perSystem = 4 - { pkgs, system, ... }: 4 + { pkgs, ... }: 5 5 let 6 - piPkg = self.legacyPackages.${system}.llm.pi; 6 + pi = inputs.wrappers.lib.wrapModule { 7 + imports = [ self.wrapperModules.pi ]; 8 + inherit pkgs; 7 9 8 - # Pre-install pi plugins into a fake npm global prefix 9 - pluginPrefixRaw = 10 - pkgs.runCommand "pi-plugins-raw" 11 - { 12 - nativeBuildInputs = [ 13 - pkgs.nodejs 14 - pkgs.cacert 15 - ]; 16 - outputHashMode = "recursive"; 17 - outputHashAlgo = "sha256"; 18 - outputHash = "sha256-QZSVCJ0XirRz56v6ogxaB37c0bI8+OGEjrnqCFr/YI8="; 19 - impureEnvVars = [ 20 - "http_proxy" 21 - "https_proxy" 22 - ]; 23 - SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 24 - } 25 - '' 26 - export HOME=$TMPDIR 27 - export npm_config_prefix=$out 28 - npm install -g pi-hooks shitty-extensions 29 - ''; 10 + settings = { 11 + packages = [ 12 + { 13 + source = "npm:pi-hooks"; 14 + hash = "sha256-IYXckWXmRsX62aQANatkcu2kV+PXT5mNB2ONpJsOPMI="; 15 + } 16 + { 17 + source = "npm:shitty-extensions"; 18 + hash = "sha256-OJZezuRDUiKysfkjJxeA6BjGohFR/+uVg611jGaRTV0="; 19 + extensions = [ "!extensions/resistance.ts" ]; 20 + } 21 + ]; 22 + defaultProvider = "anthropic"; 23 + defaultModel = "claude-opus-4-6"; 24 + defaultThinkingLevel = "medium"; 25 + permissionLevel = "low"; 26 + permissionMode = "ask"; 27 + permissionConfig.overrides.minimal = [ 28 + "nix build *" 29 + "nix eval *" 30 + "nix fmt *" 31 + ]; 32 + }; 30 33 31 - # Patch shitty-extensions: 32 - # - Remove the resistance extension (annoying terminator quote widget) 33 - # - Rebind ultrathink ctrl+u -> ctrl+shift+u (conflicts with deleteToLineStart) 34 - # - Rebind speedreading ctrl+r -> ctrl+shift+r (conflicts with renameSession) 35 - # Patch pi-hooks: 36 - # - Replace terminal bell with pw-play peon sounds in permission extension 37 - pluginPrefix = pkgs.runCommand "pi-plugins" { } '' 38 - cp -a ${pluginPrefixRaw} $out 39 - chmod -R u+w $out 40 - pkg=$out/lib/node_modules/shitty-extensions/package.json 41 - ${pkgs.jq}/bin/jq '.pi.extensions |= map(select(contains("resistance") | not))' "$pkg" > "$pkg.tmp" 42 - mv "$pkg.tmp" "$pkg" 34 + pluginOverrides = '' 35 + # Fix keybinding conflicts in extension source 36 + ${pkgs.gnused}/bin/sed -i 's/"ctrl+u"/"ctrl+shift+u"/' $out/lib/node_modules/shitty-extensions/extensions/ultrathink.ts 37 + ${pkgs.gnused}/bin/sed -i 's/"ctrl+r"/"ctrl+shift+r"/' $out/lib/node_modules/shitty-extensions/extensions/speedreading.ts 43 38 44 - # Fix keybinding conflicts in extension source 45 - ${pkgs.gnused}/bin/sed -i 's/"ctrl+u"/"ctrl+shift+u"/' $out/lib/node_modules/shitty-extensions/extensions/ultrathink.ts 46 - ${pkgs.gnused}/bin/sed -i 's/"ctrl+r"/"ctrl+shift+r"/' $out/lib/node_modules/shitty-extensions/extensions/speedreading.ts 47 - 48 - # Patch permission extension to use pw-play for peon sounds on Linux 49 - ${pkgs.python3}/bin/python3 ${./patch-permission-sound.py} \ 50 - $out/lib/node_modules/pi-hooks/permission/permission.ts \ 51 - ${pkgs.pipewire}/bin/pw-play 52 - ''; 39 + # Patch permission extension to use pw-play for peon sounds on Linux 40 + ${pkgs.python3}/bin/python3 ${./patch-permission-sound.py} \ 41 + $out/lib/node_modules/pi-hooks/permission/permission.ts \ 42 + ${pkgs.pipewire}/bin/pw-play 43 + ''; 44 + }; 53 45 in 54 46 { 55 - packages.pi = inputs.wrappers.lib.wrapPackage { 56 - pkgs = pkgs; 57 - package = piPkg; 58 - runtimeInputs = [ pkgs.nodejs ]; 59 - wrapper = 60 - { exePath, ... }: 61 - '' 62 - set -efu 63 - export npm_config_prefix="${pluginPrefix}" 64 - 65 - # Ensure settings.json has our plugins listed 66 - SETTINGS_DIR="''${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" 67 - SETTINGS_FILE="$SETTINGS_DIR/settings.json" 68 - mkdir -p "$SETTINGS_DIR" 69 - 70 - # Add packages to settings if not already present 71 - if [ ! -f "$SETTINGS_FILE" ]; then 72 - echo '{"packages":["npm:pi-hooks","npm:shitty-extensions"]}' > "$SETTINGS_FILE" 73 - else 74 - for pkg in "npm:pi-hooks" "npm:shitty-extensions"; do 75 - if ! grep -q "$pkg" "$SETTINGS_FILE"; then 76 - ${pkgs.jq}/bin/jq --arg p "$pkg" '.packages = ((.packages // []) + [$p] | unique)' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp" 77 - mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE" 78 - fi 79 - done 80 - fi 81 - 82 - exec ${exePath} "$@" 83 - ''; 84 - }; 47 + packages.pi = pi.wrapper; 85 48 }; 86 49 }
+144
wrapperModules/pi/flake-module.nix
··· 1 + { self, ... }: 2 + { 3 + flake.wrapperModules.pi = 4 + { 5 + config, 6 + lib, 7 + ... 8 + }: 9 + let 10 + pkgs = config.pkgs; 11 + 12 + # Extract npm packages with hashes from settings.packages 13 + npmPackages = lib.filter (p: builtins.isAttrs p && p ? hash) (config.settings.packages or [ ]); 14 + fetchPlugin = 15 + pkg: 16 + let 17 + source = pkg.source or pkg; 18 + name = lib.removePrefix "npm:" source; 19 + in 20 + pkgs.runCommand "pi-plugin-${name}" 21 + { 22 + nativeBuildInputs = [ 23 + pkgs.nodejs 24 + pkgs.cacert 25 + ]; 26 + outputHashMode = "recursive"; 27 + outputHashAlgo = "sha256"; 28 + outputHash = pkg.hash; 29 + impureEnvVars = [ 30 + "http_proxy" 31 + "https_proxy" 32 + ]; 33 + SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; 34 + } 35 + '' 36 + export HOME=$TMPDIR 37 + export npm_config_prefix=$out 38 + npm install -g ${name} 39 + ''; 40 + 41 + fetchedPlugins = map fetchPlugin npmPackages; 42 + 43 + # Strip hash and null-valued attrs from package entries before writing settings.json 44 + cleanPackage = 45 + p: if builtins.isAttrs p then lib.filterAttrs (k: v: k != "hash" && v != null) p else p; 46 + 47 + cleanSettings = config.settings // { 48 + packages = map cleanPackage (config.settings.packages or [ ]); 49 + }; 50 + 51 + settingsFile = pkgs.writeText "pi-settings.json" (builtins.toJSON cleanSettings); 52 + in 53 + { 54 + _class = "wrapper"; 55 + 56 + options = { 57 + settings = lib.mkOption { 58 + type = lib.types.submodule { 59 + freeformType = (pkgs.formats.json { }).type; 60 + options.packages = lib.mkOption { 61 + type = lib.types.listOf ( 62 + lib.types.either lib.types.str ( 63 + lib.types.submodule { 64 + freeformType = (pkgs.formats.json { }).type; 65 + options.hash = lib.mkOption { 66 + type = lib.types.str; 67 + description = "Fixed-output hash for fetching this npm package."; 68 + }; 69 + options.source = lib.mkOption { 70 + type = lib.types.str; 71 + description = "Package source (e.g. npm:package-name)."; 72 + }; 73 + options.extensions = lib.mkOption { 74 + type = lib.types.nullOr (lib.types.listOf lib.types.str); 75 + default = null; 76 + description = '' 77 + Extension filter patterns following pi's native syntax. 78 + When null (default), all extensions from the package are loaded. 79 + An empty list explicitly disables all extensions. 80 + Patterns use prefixes to control inclusion: 81 + - `"!pattern"` — exclude matching extensions 82 + - `"+pattern"` — force-include (overrides excludes) 83 + - `"-pattern"` — force-exclude (overrides includes) 84 + - `"path"` — plain path to include 85 + ''; 86 + example = [ 87 + "!./extensions/resistance.ts" 88 + ]; 89 + }; 90 + } 91 + ) 92 + ); 93 + default = [ ]; 94 + description = "Pi packages list. Object entries may include a `hash` for Nix fetching."; 95 + }; 96 + }; 97 + default = { }; 98 + description = "Pi settings.json content."; 99 + }; 100 + 101 + pluginOverrides = lib.mkOption { 102 + type = lib.types.str; 103 + default = ""; 104 + description = '' 105 + Shell commands to run after assembling the plugin prefix. 106 + The variable `$out` points to the mutable copy. 107 + ''; 108 + }; 109 + 110 + pluginPrefix = lib.mkOption { 111 + type = lib.types.package; 112 + description = "The final npm prefix with all plugins merged and overrides applied."; 113 + default = pkgs.runCommand "pi-plugins" { } '' 114 + mkdir -p $out 115 + ${lib.concatMapStringsSep "\n" (p: "cp -a --no-preserve=mode ${p}/* $out/") fetchedPlugins} 116 + ${lib.optionalString (config.pluginOverrides != "") config.pluginOverrides} 117 + ''; 118 + }; 119 + }; 120 + 121 + config.package = lib.mkDefault ( 122 + self.legacyPackages.${pkgs.system}.llm.pi.overrideAttrs (old: { 123 + postInstall = (old.postInstall or "") + '' 124 + patch $out/lib/node_modules/@mariozechner/pi-coding-agent/dist/core/settings-manager.js \ 125 + ${./settings-env.patch} 126 + ''; 127 + }) 128 + ); 129 + 130 + config.extraPackages = [ pkgs.nodejs ]; 131 + 132 + config.env.npm_config_prefix = "${config.pluginPrefix}"; 133 + config.env.PI_SETTINGS_FILE = "${settingsFile}"; 134 + 135 + config.meta.maintainers = [ 136 + { 137 + name = "lassulus"; 138 + github = "lassulus"; 139 + githubId = 621375; 140 + } 141 + ]; 142 + config.meta.platforms = lib.platforms.all; 143 + }; 144 + }
+20
wrapperModules/pi/settings-env.patch
··· 1 + --- /tmp/settings-manager.js.orig 2026-02-18 23:29:05.967146474 +0100 2 + +++ /tmp/settings-manager.js 2026-02-18 23:29:12.452952321 +0100 3 + @@ -97,7 +97,16 @@ 4 + /** Create a SettingsManager that loads from files */ 5 + static create(cwd = process.cwd(), agentDir = getAgentDir()) { 6 + const storage = new FileSettingsStorage(cwd, agentDir); 7 + - return SettingsManager.fromStorage(storage); 8 + + const manager = SettingsManager.fromStorage(storage); 9 + + const baseFile = process.env.PI_SETTINGS_FILE; 10 + + if (baseFile) { 11 + + try { 12 + + const baseSettings = JSON.parse(readFileSync(baseFile, "utf-8")); 13 + + manager.globalSettings = deepMergeSettings(manager.globalSettings, baseSettings); 14 + + manager.settings = deepMergeSettings(manager.globalSettings, manager.projectSettings); 15 + + } catch (e) {} 16 + + } 17 + + return manager; 18 + } 19 + /** Create a SettingsManager from an arbitrary storage backend */ 20 + static fromStorage(storage) {