-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
peon-ping: add module #8750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
peon-ping: add module #8750
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| time = "2026-02-15T12:00:00+00:00"; | ||
| condition = true; | ||
| message = '' | ||
| A new module is available: 'programs.peon-ping'. | ||
| Peon-ping is a notification sound player for AI coding agents | ||
| (Claude Code, Cursor, Codex, etc.) that plays sound effects when | ||
| tasks complete, permissions are needed, or other events occur. | ||
| ''; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,196 @@ | ||||||||||||||
| { | ||||||||||||||
| config, | ||||||||||||||
| lib, | ||||||||||||||
| pkgs, | ||||||||||||||
| ... | ||||||||||||||
| }: | ||||||||||||||
| let | ||||||||||||||
| cfg = config.programs.peon-ping; | ||||||||||||||
| jsonFormat = pkgs.formats.json { }; | ||||||||||||||
|
|
||||||||||||||
| defaultOgPacksSource = pkgs.fetchFromGitHub { | ||||||||||||||
| owner = "PeonPing"; | ||||||||||||||
| repo = "og-packs"; | ||||||||||||||
| rev = "v1.1.0"; | ||||||||||||||
| hash = "sha256-spao/GTIhH4c5HOmVc0umMvrwOaMRa4s5Pem1AWyUOw="; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+11
to
+17
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want to manage packages in Home Manager. Can either remove altogether or add it to Nixpkgs and refer to it there. |
||||||||||||||
| hookCommand = "${cfg.package}/bin/peon"; | ||||||||||||||
|
|
||||||||||||||
| hookEntry = event: { | ||||||||||||||
| matcher = ""; | ||||||||||||||
| hooks = [ | ||||||||||||||
| ( | ||||||||||||||
| { | ||||||||||||||
| type = "command"; | ||||||||||||||
| command = hookCommand; | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||
| timeout = 10; | ||||||||||||||
| } | ||||||||||||||
| // lib.optionalAttrs (event != "SessionStart") { async = true; } | ||||||||||||||
| ) | ||||||||||||||
| ]; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| skillNames = [ | ||||||||||||||
| "peon-ping-config" | ||||||||||||||
| "peon-ping-toggle" | ||||||||||||||
| "peon-ping-use" | ||||||||||||||
| ]; | ||||||||||||||
|
|
||||||||||||||
| packFiles = lib.listToAttrs ( | ||||||||||||||
| map ( | ||||||||||||||
| name: | ||||||||||||||
| lib.nameValuePair ".claude/hooks/peon-ping/packs/${name}" { | ||||||||||||||
| source = "${cfg.ogPacksSource}/${name}"; | ||||||||||||||
| recursive = true; | ||||||||||||||
| } | ||||||||||||||
| ) cfg.packs | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| skillFiles = lib.listToAttrs ( | ||||||||||||||
| map ( | ||||||||||||||
| name: | ||||||||||||||
| lib.nameValuePair ".claude/skills/${name}" { | ||||||||||||||
| source = "${cfg.package.src}/skills/${name}"; | ||||||||||||||
| recursive = true; | ||||||||||||||
| } | ||||||||||||||
| ) skillNames | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| claudeCodeHooks = lib.listToAttrs ( | ||||||||||||||
| map (event: lib.nameValuePair event [ (hookEntry event) ]) cfg.claudeCodeHookEvents | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||
| ); | ||||||||||||||
| in | ||||||||||||||
| { | ||||||||||||||
| meta.maintainers = [ lib.maintainers.workflow ]; | ||||||||||||||
|
|
||||||||||||||
| options.programs.peon-ping = { | ||||||||||||||
| enable = lib.mkEnableOption "peon-ping, a notification sound player for AI coding agents"; | ||||||||||||||
|
|
||||||||||||||
| package = lib.mkPackageOption pkgs "peon-ping" { }; | ||||||||||||||
|
|
||||||||||||||
| settings = lib.mkOption { | ||||||||||||||
| inherit (jsonFormat) type; | ||||||||||||||
| default = { }; | ||||||||||||||
| example = { | ||||||||||||||
| active_pack = "peon"; | ||||||||||||||
| volume = 0.5; | ||||||||||||||
| enabled = true; | ||||||||||||||
| desktop_notifications = true; | ||||||||||||||
| categories = { | ||||||||||||||
| "session.start" = true; | ||||||||||||||
| "task.complete" = true; | ||||||||||||||
| "input.required" = true; | ||||||||||||||
| }; | ||||||||||||||
| }; | ||||||||||||||
| description = '' | ||||||||||||||
| Declarative peon-ping configuration written to | ||||||||||||||
| {file}`~/.claude/hooks/peon-ping/config.json`. | ||||||||||||||
|
|
||||||||||||||
| When non-empty, the config file is managed by Home Manager as an | ||||||||||||||
| immutable symlink. When left empty (the default), a mutable default | ||||||||||||||
| config is seeded on first activation so that the `peon` CLI and | ||||||||||||||
| Claude Code skills can modify it at runtime. | ||||||||||||||
| ''; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| packs = lib.mkOption { | ||||||||||||||
| type = lib.types.listOf lib.types.str; | ||||||||||||||
| default = [ "peon" ]; | ||||||||||||||
| example = [ | ||||||||||||||
| "peon" | ||||||||||||||
| "peon_de" | ||||||||||||||
| "aoe2" | ||||||||||||||
| ]; | ||||||||||||||
| description = '' | ||||||||||||||
| Sound pack names to install from {option}`ogPacksSource`. | ||||||||||||||
| Each name corresponds to a subdirectory in the og-packs repository. | ||||||||||||||
| ''; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| ogPacksSource = lib.mkOption { | ||||||||||||||
| type = lib.types.package; | ||||||||||||||
| default = defaultOgPacksSource; | ||||||||||||||
| defaultText = lib.literalExpression '' | ||||||||||||||
| pkgs.fetchFromGitHub { | ||||||||||||||
| owner = "PeonPing"; | ||||||||||||||
| repo = "og-packs"; | ||||||||||||||
| rev = "v1.1.0"; | ||||||||||||||
| hash = "sha256-spao/GTIhH4c5HOmVc0umMvrwOaMRa4s5Pem1AWyUOw="; | ||||||||||||||
| } | ||||||||||||||
| ''; | ||||||||||||||
| description = '' | ||||||||||||||
| Source derivation containing sound packs. Pack names in | ||||||||||||||
| {option}`packs` are resolved as subdirectories of this source. | ||||||||||||||
| ''; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| enableClaudeCodeIntegration = lib.mkOption { | ||||||||||||||
| type = lib.types.bool; | ||||||||||||||
| default = false; | ||||||||||||||
| description = '' | ||||||||||||||
| Whether to automatically configure Claude Code hooks and skills | ||||||||||||||
| for peon-ping integration. | ||||||||||||||
|
|
||||||||||||||
| Requires {option}`programs.claude-code.enable` to be set. | ||||||||||||||
| ''; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| claudeCodeHookEvents = lib.mkOption { | ||||||||||||||
| type = lib.types.listOf lib.types.str; | ||||||||||||||
| default = [ | ||||||||||||||
| "SessionStart" | ||||||||||||||
| "SessionEnd" | ||||||||||||||
| "UserPromptSubmit" | ||||||||||||||
| "Stop" | ||||||||||||||
| "Notification" | ||||||||||||||
| "PermissionRequest" | ||||||||||||||
| ]; | ||||||||||||||
| description = '' | ||||||||||||||
| Claude Code hook events to register peon-ping for. | ||||||||||||||
| Each event fires the `peon` command which reads the event | ||||||||||||||
| from stdin and plays the appropriate sound. | ||||||||||||||
| ''; | ||||||||||||||
| }; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| config = lib.mkIf cfg.enable { | ||||||||||||||
| assertions = [ | ||||||||||||||
| { | ||||||||||||||
| assertion = !cfg.enableClaudeCodeIntegration || config.programs.claude-code.enable; | ||||||||||||||
| message = '' | ||||||||||||||
| `programs.peon-ping.enableClaudeCodeIntegration` requires | ||||||||||||||
| `programs.claude-code.enable` to be set. | ||||||||||||||
| ''; | ||||||||||||||
| } | ||||||||||||||
| ]; | ||||||||||||||
|
|
||||||||||||||
| home.packages = [ cfg.package ]; | ||||||||||||||
|
|
||||||||||||||
| home.file = | ||||||||||||||
| packFiles | ||||||||||||||
| // lib.optionalAttrs cfg.enableClaudeCodeIntegration skillFiles | ||||||||||||||
| // { | ||||||||||||||
| ".claude/hooks/peon-ping/config.json" = lib.mkIf (cfg.settings != { }) { | ||||||||||||||
| source = jsonFormat.generate "peon-ping-config.json" cfg.settings; | ||||||||||||||
| }; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| home.activation.seedPeonPingConfig = lib.mkIf (cfg.settings == { }) ( | ||||||||||||||
| lib.hm.dag.entryAfter [ "linkGeneration" ] '' | ||||||||||||||
| peonConfigDir="''${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/peon-ping" | ||||||||||||||
| peonConfigFile="$peonConfigDir/config.json" | ||||||||||||||
|
Comment on lines
+181
to
+182
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to unset these variables at the end of the code block |
||||||||||||||
| if [ ! -f "$peonConfigFile" ]; then | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| run mkdir -p "$peonConfigDir" | ||||||||||||||
| run cp "${cfg.package}/lib/peon-ping/config.json" "$peonConfigFile" | ||||||||||||||
| run chmod u+w "$peonConfigFile" | ||||||||||||||
|
Comment on lines
+184
to
+186
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can use the VERBOSE_ARG variable to add
Suggested change
|
||||||||||||||
| verboseEcho "Seeded peon-ping default config at $peonConfigFile" | ||||||||||||||
| fi | ||||||||||||||
| '' | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| programs.claude-code.settings = lib.mkIf cfg.enableClaudeCodeIntegration { | ||||||||||||||
| hooks = claudeCodeHooks; | ||||||||||||||
| }; | ||||||||||||||
| }; | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| programs.peon-ping = { | ||
| enable = true; | ||
| packs = [ ]; | ||
| }; | ||
|
|
||
| test.stubs.peon-ping = { }; | ||
|
|
||
| nmt.script = '' | ||
| assertPathNotExists home-files/.claude/hooks/peon-ping/config.json | ||
| assertPathNotExists home-files/.claude/settings.json | ||
| ''; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { config, ... }: | ||
| { | ||
| programs.claude-code.enable = true; | ||
|
|
||
| programs.peon-ping = { | ||
| enable = true; | ||
| enableClaudeCodeIntegration = true; | ||
| packs = [ ]; | ||
| }; | ||
|
|
||
| test.stubs.peon-ping = { | ||
| extraAttrs.src = config.lib.test.mkStubPackage { | ||
| name = "peon-ping-src"; | ||
| buildScript = '' | ||
| mkdir -p $out/skills/peon-ping-config | ||
| mkdir -p $out/skills/peon-ping-toggle | ||
| mkdir -p $out/skills/peon-ping-use | ||
| echo "# Config skill" > $out/skills/peon-ping-config/SKILL.md | ||
| echo "# Toggle skill" > $out/skills/peon-ping-toggle/SKILL.md | ||
| echo "# Use skill" > $out/skills/peon-ping-use/SKILL.md | ||
| ''; | ||
| }; | ||
| }; | ||
|
|
||
| nmt.script = '' | ||
| assertFileExists home-files/.claude/settings.json | ||
| assertFileContent home-files/.claude/settings.json \ | ||
| ${./expected-settings.json} | ||
|
|
||
| assertFileExists home-files/.claude/skills/peon-ping-config/SKILL.md | ||
| assertFileExists home-files/.claude/skills/peon-ping-toggle/SKILL.md | ||
| assertFileExists home-files/.claude/skills/peon-ping-use/SKILL.md | ||
| ''; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| peon-ping-basic = ./basic.nix; | ||
| peon-ping-settings = ./settings.nix; | ||
| peon-ping-packs = ./packs.nix; | ||
| peon-ping-claude-code-integration = ./claude-code-integration.nix; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "active_pack": "glados", | ||
| "categories": { | ||
| "input.required": false, | ||
| "session.start": true, | ||
| "task.complete": true | ||
| }, | ||
| "desktop_notifications": false, | ||
| "enabled": true, | ||
| "volume": 0.8 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | ||
| "hooks": { | ||
| "Notification": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "async": true, | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ], | ||
| "PermissionRequest": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "async": true, | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ], | ||
| "SessionEnd": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "async": true, | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ], | ||
| "SessionStart": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ], | ||
| "Stop": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "async": true, | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ], | ||
| "UserPromptSubmit": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "async": true, | ||
| "command": "@peon-ping@/bin/peon", | ||
| "timeout": 10, | ||
| "type": "command" | ||
| } | ||
| ], | ||
| "matcher": "" | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| { config, ... }: | ||
| { | ||
| programs.peon-ping = { | ||
| enable = true; | ||
| packs = [ | ||
| "peon" | ||
| "glados" | ||
| ]; | ||
| ogPacksSource = config.lib.test.mkStubPackage { | ||
| name = "test-og-packs"; | ||
| buildScript = '' | ||
| mkdir -p $out/peon/sounds $out/glados/sounds | ||
| echo '{}' > $out/peon/openpeon.json | ||
| echo '{}' > $out/glados/openpeon.json | ||
| ''; | ||
| }; | ||
| }; | ||
|
|
||
| test.stubs.peon-ping = { }; | ||
|
|
||
| nmt.script = '' | ||
| assertDirectoryExists home-files/.claude/hooks/peon-ping/packs/peon | ||
| assertDirectoryExists home-files/.claude/hooks/peon-ping/packs/glados | ||
| ''; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why start the entry with an empty line?