agent-noti 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # agent-noti
2
+
3
+ Audio notifications for Claude Code. Hear when Claude is done or needs your input.
4
+
5
+ Works on macOS, Linux, and Windows.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g agent-noti
11
+ ```
12
+
13
+ That's it. Hooks are added automatically. Restart Claude Code.
14
+
15
+ ## What it does
16
+
17
+ - **Claude finishes** → plays `idle.mp3`
18
+ - **Claude needs input** (permission request) → plays `input.mp3`
19
+
20
+ ## Commands
21
+
22
+ ```sh
23
+ agent-noti test # Play both sounds
24
+ agent-noti install # Re-add hooks (if needed)
25
+ agent-noti uninstall # Remove hooks
26
+ ```
27
+
28
+ ## Uninstall
29
+
30
+ ```sh
31
+ npm uninstall -g agent-noti
32
+ ```
33
+
34
+ Hooks are removed automatically.
35
+
36
+ ## Custom sounds
37
+
38
+ Replace the mp3 files in the package's `sounds/` directory:
39
+
40
+ ```sh
41
+ agent-noti test # find where sounds are located
42
+ ```
43
+
44
+ ## Platform support
45
+
46
+ | OS | Audio player |
47
+ |---|---|
48
+ | macOS | `afplay` (built-in) |
49
+ | Linux | `ffplay`, `paplay`, or `mpv` |
50
+ | Windows | PowerShell MediaPlayer |
51
+
52
+ ## License
53
+
54
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { execSync } from "child_process";
6
+ import { fileURLToPath } from "url";
7
+ import { homedir } from "os";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const PKG_ROOT = join(__dirname, "..");
11
+ const PLAY_SCRIPT = join(__dirname, "play.mjs");
12
+ const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
13
+
14
+ const HOOK_ID = "agent-noti";
15
+
16
+ function buildHooks() {
17
+ const idle = { type: "command", command: `node "${PLAY_SCRIPT}" idle` };
18
+ const input = { type: "command", command: `node "${PLAY_SCRIPT}" input` };
19
+
20
+ return {
21
+ Stop: [{ hooks: [idle], metadata: { id: HOOK_ID } }],
22
+ PermissionRequest: [{ hooks: [input], metadata: { id: HOOK_ID } }],
23
+ };
24
+ }
25
+
26
+ function readSettings() {
27
+ const dir = dirname(SETTINGS_PATH);
28
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
29
+ if (!existsSync(SETTINGS_PATH)) return {};
30
+ return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
31
+ }
32
+
33
+ function writeSettings(settings) {
34
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
35
+ }
36
+
37
+ function removeOurHooks(hookEntries) {
38
+ if (!Array.isArray(hookEntries)) return hookEntries;
39
+ return hookEntries.filter((e) => !e.metadata || e.metadata.id !== HOOK_ID);
40
+ }
41
+
42
+ function install() {
43
+ const settings = readSettings();
44
+ if (!settings.hooks) settings.hooks = {};
45
+
46
+ for (const [event, entries] of Object.entries(buildHooks())) {
47
+ const existing = removeOurHooks(settings.hooks[event] || []);
48
+ settings.hooks[event] = [...existing, ...entries];
49
+ }
50
+
51
+ writeSettings(settings);
52
+
53
+ console.log("");
54
+ console.log(" agent-noti installed!");
55
+ console.log(" Restart Claude Code to activate.");
56
+ console.log("");
57
+ console.log(" agent-noti test Play sounds");
58
+ console.log(" agent-noti uninstall Remove hooks");
59
+ console.log("");
60
+ }
61
+
62
+ function uninstall() {
63
+ const settings = readSettings();
64
+ if (!settings.hooks) {
65
+ console.log("Nothing to uninstall.");
66
+ return;
67
+ }
68
+
69
+ for (const event of Object.keys(settings.hooks)) {
70
+ const cleaned = removeOurHooks(settings.hooks[event]);
71
+ if (cleaned.length === 0) delete settings.hooks[event];
72
+ else settings.hooks[event] = cleaned;
73
+ }
74
+
75
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
76
+ writeSettings(settings);
77
+
78
+ console.log("");
79
+ console.log(" agent-noti uninstalled!");
80
+ console.log("");
81
+ }
82
+
83
+ function test() {
84
+ console.log("");
85
+ for (const name of ["idle", "input"]) {
86
+ console.log(` Playing: ${name}`);
87
+ execSync(`node "${PLAY_SCRIPT}" ${name}`, { stdio: "inherit" });
88
+ // Small pause between sounds
89
+ execSync(process.platform === "win32" ? "timeout /t 1 >nul" : "sleep 1");
90
+ }
91
+ console.log("");
92
+ }
93
+
94
+ const cmd = process.argv[2];
95
+
96
+ switch (cmd) {
97
+ case "install": install(); break;
98
+ case "uninstall": uninstall(); break;
99
+ case "test": test(); break;
100
+ default:
101
+ console.log("");
102
+ console.log(" agent-noti install Add hooks to Claude Code");
103
+ console.log(" agent-noti uninstall Remove hooks");
104
+ console.log(" agent-noti test Play sounds");
105
+ console.log("");
106
+ }
package/bin/play.mjs ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cross-platform audio player. Called directly by Claude Code hooks.
4
+ * Usage: node play.mjs <idle|input>
5
+ */
6
+
7
+ import { execFile, exec } from "child_process";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import { platform } from "os";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const SOUNDS_DIR = join(__dirname, "..", "sounds");
14
+ const sound = process.argv[2];
15
+
16
+ if (!sound) process.exit(0);
17
+
18
+ const file = join(SOUNDS_DIR, `${sound}.mp3`);
19
+ const os = platform();
20
+
21
+ if (os === "darwin") {
22
+ execFile("afplay", [file], () => {});
23
+ } else if (os === "win32") {
24
+ exec(
25
+ `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([uri]'${file.replace(/'/g, "''")}'); $p.Play(); Start-Sleep -Seconds 3"`,
26
+ () => {}
27
+ );
28
+ } else {
29
+ // Linux: try players in order
30
+ execFile("ffplay", ["-nodisp", "-autoexit", "-loglevel", "quiet", file], (err) => {
31
+ if (err) execFile("paplay", [file], (err2) => {
32
+ if (err2) execFile("mpv", ["--no-video", file], () => {});
33
+ });
34
+ });
35
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "agent-noti",
3
+ "version": "1.0.0",
4
+ "description": "Audio notifications for Claude Code — hear when Claude is done or needs your input",
5
+ "bin": {
6
+ "agent-noti": "./bin/cli.mjs"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "sounds/"
11
+ ],
12
+ "scripts": {
13
+ "postinstall": "node bin/cli.mjs install",
14
+ "preuninstall": "node bin/cli.mjs uninstall"
15
+ },
16
+ "os": ["darwin", "linux", "win32"],
17
+ "keywords": ["claude", "claude-code", "notifications", "audio", "hooks"],
18
+ "license": "MIT"
19
+ }
Binary file
Binary file