opencode-notification-macos 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Evan Stinger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,296 @@
1
+ # opencode-notification-macos
2
+
3
+ Native macOS notifications and sounds for [OpenCode](https://opencode.ai) - zero external dependencies.
4
+
5
+ [![npm version](https://badge.fury.io/js/opencode-notification-macos.svg)](https://www.npmjs.com/package/opencode-notification-macos)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ 🔔 **Native macOS Notifications** - Uses `osascript` for system notifications
11
+ 🔊 **System Sounds** - Uses `afplay` with all 14 built-in macOS sounds
12
+ ⚙️ **Configurable** - Per-event sound customization
13
+ 📁 **Dual Config** - Global + project-level configuration
14
+ 🎯 **Zero Dependencies** - No external libraries, just native macOS commands
15
+
16
+ ## Installation
17
+
18
+ ### For AI Agents (Automated Setup)
19
+
20
+ If you're an AI agent setting this up for a user, run these commands:
21
+
22
+ ```bash
23
+ # 1. Install the plugin globally
24
+ npm install -g opencode-notification-macos
25
+
26
+ # 2. Configure default sounds interactively
27
+ npx opencode-notification-macos config
28
+
29
+ # 3. Add to OpenCode configuration
30
+ cat >> ~/.config/opencode/opencode.jsonc << 'EOF'
31
+ {
32
+ "plugin": [
33
+ "opencode-notification-macos"
34
+ ]
35
+ }
36
+ EOF
37
+ ```
38
+
39
+ ### Manual Installation
40
+
41
+ ```bash
42
+ npm install -g opencode-notification-macos
43
+ ```
44
+
45
+ Then add to your `~/.config/opencode/opencode.jsonc`:
46
+
47
+ ```json
48
+ {
49
+ "plugin": [
50
+ "opencode-notification-macos"
51
+ ]
52
+ }
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ ### Interactive Configuration (Recommended)
58
+
59
+ ```bash
60
+ # Configure sounds interactively
61
+ npx opencode-notification-macos config
62
+
63
+ # List available sounds
64
+ npx opencode-notification-macos list
65
+
66
+ # Preview a sound
67
+ npx opencode-notification-macos preview
68
+
69
+ # Show current configuration
70
+ npx opencode-notification-macos show
71
+
72
+ # Reset to defaults
73
+ npx opencode-notification-macos reset
74
+ ```
75
+
76
+ ### Configuration Files
77
+
78
+ The plugin supports two configuration locations (project config overrides global):
79
+
80
+ **Global** (applies to all projects):
81
+ ```
82
+ ~/.config/opencode/opencode-notification-macos.json
83
+ ```
84
+
85
+ **Project** (applies to specific project):
86
+ ```
87
+ .opencode/notification.json
88
+ ```
89
+
90
+ ### Configuration Format
91
+
92
+ ```json
93
+ {
94
+ "sound": true,
95
+ "notification": true,
96
+ "showProjectName": true,
97
+ "events": {
98
+ "complete": { "sound": true, "notification": true },
99
+ "error": { "sound": true, "notification": true },
100
+ "question": { "sound": true, "notification": true },
101
+ "permission": { "sound": true, "notification": true },
102
+ "subagent_complete": { "sound": false, "notification": false }
103
+ },
104
+ "messages": {
105
+ "complete": "✅ Task completed",
106
+ "error": "❌ Error occurred",
107
+ "question": "❓ OpenCode needs your input",
108
+ "permission": "🔐 Permission required",
109
+ "subagent_complete": "Subagent finished"
110
+ },
111
+ "sounds": {
112
+ "complete": "Glass",
113
+ "error": "Basso",
114
+ "question": "Ping",
115
+ "permission": "Tink",
116
+ "subagent_complete": "Pop"
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Available Sounds
122
+
123
+ All 14 macOS system sounds are available:
124
+
125
+ - `Basso` - Low alert sound
126
+ - `Blow` - Airy whoosh
127
+ - `Bottle` - Cork pop
128
+ - `Frog` - Frog croak
129
+ - `Funk` - Funk guitar riff
130
+ - `Glass` - Glass tap (default for completion)
131
+ - `Hero` - Heroic fanfare
132
+ - `Morse` - Morse code beeps
133
+ - `Ping` - Simple ping (default for questions)
134
+ - `Pop` - Pop sound
135
+ - `Purr` - Cat purr
136
+ - `Sosumi` - Classic Mac sound
137
+ - `Submarine` - Sonar ping
138
+ - `Tink` - Light bell (default for permissions)
139
+
140
+ ## Events
141
+
142
+ The plugin triggers on these OpenCode events:
143
+
144
+ | Event | Description | Default Sound |
145
+ |-------|-------------|---------------|
146
+ | `complete` | Main task finished | Glass |
147
+ | `error` | An error occurred | Basso |
148
+ | `question` | OpenCode needs input | Ping |
149
+ | `permission` | Permission required | Tink |
150
+ | `subagent_complete` | Background task finished | Pop |
151
+
152
+ ## AI Agent Setup Instructions
153
+
154
+ If you are an AI assistant reading this to set up the plugin for a user, follow these exact steps:
155
+
156
+ ### Step 1: Check Prerequisites
157
+
158
+ ```bash
159
+ # Verify macOS
160
+ sw_vers -productName
161
+
162
+ # Verify OpenCode is installed
163
+ which opencode
164
+ ```
165
+
166
+ ### Step 2: Install Plugin
167
+
168
+ ```bash
169
+ npm install -g opencode-notification-macos
170
+ ```
171
+
172
+ ### Step 3: Create Configuration
173
+
174
+ Option A - Interactive (recommended):
175
+ ```bash
176
+ npx opencode-notification-macos config
177
+ ```
178
+
179
+ Option B - Create config file directly:
180
+ ```bash
181
+ mkdir -p ~/.config/opencode
182
+ cat > ~/.config/opencode/opencode-notification-macos.json << 'EOF'
183
+ {
184
+ "sound": true,
185
+ "notification": true,
186
+ "showProjectName": true,
187
+ "sounds": {
188
+ "complete": "Glass",
189
+ "error": "Basso",
190
+ "question": "Ping",
191
+ "permission": "Tink",
192
+ "subagent_complete": "Pop"
193
+ }
194
+ }
195
+ EOF
196
+ ```
197
+
198
+ ### Step 4: Update OpenCode Config
199
+
200
+ ```bash
201
+ # Read current config
202
+ cat ~/.config/opencode/opencode.jsonc
203
+
204
+ # Add plugin to the plugin array
205
+ # If opencode.jsonc exists, add "opencode-notification-macos" to the plugin array
206
+ # If it doesn't exist, create it with the plugin
207
+ ```
208
+
209
+ ### Step 5: Test
210
+
211
+ ```bash
212
+ # Test sound
213
+ npx opencode-notification-macos preview
214
+
215
+ # Test with OpenCode
216
+ opencode "say hello and wait for my response"
217
+ ```
218
+
219
+ ## Troubleshooting
220
+
221
+ ### No sound playing?
222
+
223
+ 1. Check system volume:
224
+ ```bash
225
+ afplay /System/Library/Sounds/Glass.aiff
226
+ ```
227
+
228
+ 2. Check notification permissions:
229
+ - System Settings → Notifications → Script Editor
230
+ - Ensure notifications are enabled
231
+
232
+ 3. Verify config:
233
+ ```bash
234
+ npx opencode-notification-macos show
235
+ ```
236
+
237
+ ### Plugin not loading?
238
+
239
+ 1. Check OpenCode config syntax:
240
+ ```bash
241
+ cat ~/.config/opencode/opencode.jsonc
242
+ ```
243
+
244
+ 2. Clear OpenCode cache:
245
+ ```bash
246
+ rm -rf ~/.cache/opencode
247
+ ```
248
+
249
+ 3. Restart OpenCode
250
+
251
+ ### Permission denied errors?
252
+
253
+ The plugin uses `osascript` for notifications. Grant permissions:
254
+ - System Settings → Privacy & Security → Automation
255
+ - Ensure your terminal has permission to control System Events
256
+
257
+ ## Development
258
+
259
+ ```bash
260
+ # Clone repository
261
+ git clone https://github.com/evanstinger/opencode-notification-macos.git
262
+ cd opencode-notification-macos
263
+
264
+ # Install dependencies
265
+ npm install
266
+
267
+ # Build
268
+ npm run build
269
+
270
+ # Watch mode
271
+ npm run dev
272
+
273
+ # Type check
274
+ npm run typecheck
275
+ ```
276
+
277
+ ## How It Works
278
+
279
+ This plugin replaces external notification libraries with native macOS commands:
280
+
281
+ - **Sounds**: Uses `afplay` (built into macOS) to play system sounds from `/System/Library/Sounds/`
282
+ - **Notifications**: Uses `osascript` (AppleScript) to display native macOS notifications
283
+ - **Zero Dependencies**: No npm packages required for core functionality
284
+
285
+ ## License
286
+
287
+ MIT © [Evan Stinger](https://github.com/evanstinger)
288
+
289
+ ## Contributing
290
+
291
+ Contributions welcome! Please read the [contributing guide](CONTRIBUTING.md) first.
292
+
293
+ ## Related
294
+
295
+ - [OpenCode](https://opencode.ai) - The AI coding agent
296
+ - [@mohak34/opencode-notifier](https://www.npmjs.com/package/@mohak34/opencode-notifier) - Cross-platform notifier (uses node-notifier)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { homedir } from 'os';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ const execAsync = promisify(exec);
8
+ const SYSTEM_SOUNDS_PATH = '/System/Library/Sounds';
9
+ const DEFAULT_CONFIG = {
10
+ sound: true,
11
+ notification: true,
12
+ showProjectName: true,
13
+ sounds: {
14
+ complete: 'Glass',
15
+ error: 'Basso',
16
+ question: 'Ping',
17
+ permission: 'Tink',
18
+ subagent_complete: 'Pop'
19
+ }
20
+ };
21
+ function getGlobalConfigPath() {
22
+ return join(homedir(), '.config', 'opencode', 'opencode-notification-macos.json');
23
+ }
24
+ function listAvailableSounds() {
25
+ return [
26
+ 'Basso', 'Blow', 'Bottle', 'Frog', 'Funk', 'Glass',
27
+ 'Hero', 'Morse', 'Ping', 'Pop', 'Purr', 'Sosumi',
28
+ 'Submarine', 'Tink'
29
+ ];
30
+ }
31
+ async function playSound(soundName) {
32
+ const soundPath = join(SYSTEM_SOUNDS_PATH, `${soundName}.aiff`);
33
+ try {
34
+ await execAsync(`afplay "${soundPath}"`);
35
+ }
36
+ catch (e) {
37
+ // Silently fail
38
+ }
39
+ }
40
+ async function listSounds() {
41
+ console.log('\n🔊 Available macOS System Sounds:\n');
42
+ const sounds = listAvailableSounds();
43
+ sounds.forEach((sound, index) => {
44
+ console.log(` ${index + 1}. ${sound}`);
45
+ });
46
+ console.log();
47
+ }
48
+ async function previewSound(args) {
49
+ const soundIndex = parseInt(args[0]) - 1;
50
+ const sounds = listAvailableSounds();
51
+ if (isNaN(soundIndex) || soundIndex < 0 || soundIndex >= sounds.length) {
52
+ console.log('\n🔊 Available sounds:\n');
53
+ sounds.forEach((sound, index) => {
54
+ console.log(` ${index + 1}. ${sound}`);
55
+ });
56
+ console.log('\nUsage: npx opencode-notification-macos preview <number>');
57
+ console.log('Example: npx opencode-notification-macos preview 6\n');
58
+ return;
59
+ }
60
+ const sound = sounds[soundIndex];
61
+ console.log(`\n🔊 Playing ${sound}...`);
62
+ await playSound(sound);
63
+ console.log('✅ Done\n');
64
+ }
65
+ async function showConfig() {
66
+ const configPath = getGlobalConfigPath();
67
+ if (!existsSync(configPath)) {
68
+ console.log('\n⚙️ No configuration found. Using defaults:\n');
69
+ console.log(JSON.stringify(DEFAULT_CONFIG, null, 2));
70
+ console.log(`\nConfig path: ${configPath}\n`);
71
+ return;
72
+ }
73
+ try {
74
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
75
+ console.log('\n📋 Current Configuration:\n');
76
+ console.log(JSON.stringify(config, null, 2));
77
+ console.log(`\nConfig path: ${configPath}\n`);
78
+ }
79
+ catch (e) {
80
+ console.error('❌ Error reading config:', e);
81
+ }
82
+ }
83
+ async function resetConfig() {
84
+ const configPath = getGlobalConfigPath();
85
+ try {
86
+ mkdirSync(dirname(configPath), { recursive: true });
87
+ writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
88
+ console.log('\n✅ Configuration reset to defaults\n');
89
+ console.log(`Config saved to: ${configPath}\n`);
90
+ }
91
+ catch (e) {
92
+ console.error('❌ Error saving config:', e);
93
+ }
94
+ }
95
+ async function setSound(args) {
96
+ const [event, soundName] = args;
97
+ const validEvents = ['complete', 'error', 'question', 'permission', 'subagent_complete'];
98
+ const validSounds = listAvailableSounds();
99
+ if (!event || !soundName) {
100
+ console.log('\nUsage: npx opencode-notification-macos set-sound <event> <sound>');
101
+ console.log('\nValid events:', validEvents.join(', '));
102
+ console.log('Valid sounds:', validSounds.join(', '));
103
+ console.log('\nExample: npx opencode-notification-macos set-sound complete Glass\n');
104
+ return;
105
+ }
106
+ if (!validEvents.includes(event)) {
107
+ console.error(`❌ Invalid event: ${event}`);
108
+ console.log('Valid events:', validEvents.join(', '));
109
+ return;
110
+ }
111
+ if (!validSounds.includes(soundName)) {
112
+ console.error(`❌ Invalid sound: ${soundName}`);
113
+ console.log('Valid sounds:', validSounds.join(', '));
114
+ return;
115
+ }
116
+ const configPath = getGlobalConfigPath();
117
+ let config = DEFAULT_CONFIG;
118
+ if (existsSync(configPath)) {
119
+ try {
120
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
121
+ }
122
+ catch (e) {
123
+ // Use defaults
124
+ }
125
+ }
126
+ config.sounds[event] = soundName;
127
+ try {
128
+ mkdirSync(dirname(configPath), { recursive: true });
129
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
130
+ console.log(`\n✅ Set ${event} sound to ${soundName}\n`);
131
+ // Preview the sound
132
+ console.log(`🔊 Previewing ${soundName}...`);
133
+ await playSound(soundName);
134
+ }
135
+ catch (e) {
136
+ console.error('❌ Error saving config:', e);
137
+ }
138
+ }
139
+ async function main() {
140
+ const command = process.argv[2];
141
+ const args = process.argv.slice(3);
142
+ switch (command) {
143
+ case 'list':
144
+ await listSounds();
145
+ break;
146
+ case 'preview':
147
+ await previewSound(args);
148
+ break;
149
+ case 'config':
150
+ case 'show':
151
+ await showConfig();
152
+ break;
153
+ case 'reset':
154
+ await resetConfig();
155
+ break;
156
+ case 'set-sound':
157
+ await setSound(args);
158
+ break;
159
+ default:
160
+ console.log(`
161
+ 🔔 OpenCode Notification macOS
162
+
163
+ Usage:
164
+ npx opencode-notification-macos <command> [options]
165
+
166
+ Commands:
167
+ list List all available macOS system sounds
168
+ preview <number> Preview a sound by number (1-14)
169
+ config, show Show current configuration
170
+ reset Reset configuration to defaults
171
+ set-sound <event> <sound> Set sound for an event
172
+
173
+ Examples:
174
+ npx opencode-notification-macos list
175
+ npx opencode-notification-macos preview 6
176
+ npx opencode-notification-macos set-sound complete Glass
177
+ npx opencode-notification-macos set-sound error Basso
178
+
179
+ Configuration file:
180
+ ~/.config/opencode/opencode-notification-macos.json
181
+
182
+ Events:
183
+ complete - Task completed
184
+ error - Error occurred
185
+ question - OpenCode needs input
186
+ permission - Permission required
187
+ subagent_complete - Subagent task finished
188
+ `);
189
+ }
190
+ }
191
+ main().catch(console.error);
192
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { PluginConfig } from './types.js';
2
+ export declare function getGlobalConfigPath(): string;
3
+ export declare function getProjectConfigPath(): string;
4
+ export declare function loadConfig(): PluginConfig;
5
+ export declare function saveConfig(config: PluginConfig, isGlobal?: boolean): void;
6
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAE1D,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,UAAU,IAAI,YAAY,CA2BzC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,GAAE,OAAc,GAAG,IAAI,CAU/E"}
package/dist/config.js ADDED
@@ -0,0 +1,57 @@
1
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { homedir } from 'os';
4
+ import { DEFAULT_CONFIG } from './types.js';
5
+ export function getGlobalConfigPath() {
6
+ return join(homedir(), '.config', 'opencode', 'opencode-notification-macos.json');
7
+ }
8
+ export function getProjectConfigPath() {
9
+ return join(process.cwd(), '.opencode', 'notification.json');
10
+ }
11
+ export function loadConfig() {
12
+ const globalPath = getGlobalConfigPath();
13
+ const projectPath = getProjectConfigPath();
14
+ let config = { ...DEFAULT_CONFIG };
15
+ // Load global config
16
+ if (existsSync(globalPath)) {
17
+ try {
18
+ const globalConfig = JSON.parse(readFileSync(globalPath, 'utf8'));
19
+ config = mergeConfig(config, globalConfig);
20
+ }
21
+ catch (e) {
22
+ console.error('[opencode-notification-macos] Failed to load global config:', e);
23
+ }
24
+ }
25
+ // Load project config (overrides global)
26
+ if (existsSync(projectPath)) {
27
+ try {
28
+ const projectConfig = JSON.parse(readFileSync(projectPath, 'utf8'));
29
+ config = mergeConfig(config, projectConfig);
30
+ }
31
+ catch (e) {
32
+ console.error('[opencode-notification-macos] Failed to load project config:', e);
33
+ }
34
+ }
35
+ return config;
36
+ }
37
+ export function saveConfig(config, isGlobal = true) {
38
+ const configPath = isGlobal ? getGlobalConfigPath() : getProjectConfigPath();
39
+ try {
40
+ mkdirSync(dirname(configPath), { recursive: true });
41
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
42
+ }
43
+ catch (e) {
44
+ console.error('[opencode-notification-macos] Failed to save config:', e);
45
+ throw e;
46
+ }
47
+ }
48
+ function mergeConfig(base, override) {
49
+ return {
50
+ ...base,
51
+ ...override,
52
+ events: { ...base.events, ...override.events },
53
+ messages: { ...base.messages, ...override.messages },
54
+ sounds: { ...base.sounds, ...override.sounds }
55
+ };
56
+ }
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAgB,cAAc,EAAE,MAAM,YAAY,CAAC;AAE1D,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,kCAAkC,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IACzC,MAAM,WAAW,GAAG,oBAAoB,EAAE,CAAC;IAE3C,IAAI,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IAEnC,qBAAqB;IACrB,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;YAClE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,6DAA6D,EAAE,CAAC,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;YACpE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8DAA8D,EAAE,CAAC,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAoB,EAAE,WAAoB,IAAI;IACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAE7E,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,sDAAsD,EAAE,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAkB,EAAE,QAA+B;IACtE,OAAO;QACL,GAAG,IAAI;QACP,GAAG,QAAQ;QACX,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE;QAC9C,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE;QACpD,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ export interface PluginContext {
2
+ client: {
3
+ session: {
4
+ get: (params: {
5
+ path: {
6
+ id: string;
7
+ };
8
+ }) => Promise<{
9
+ data?: {
10
+ parentID?: string;
11
+ };
12
+ }>;
13
+ };
14
+ };
15
+ directory: string;
16
+ }
17
+ export interface Event {
18
+ type: string;
19
+ data?: {
20
+ sessionID?: string;
21
+ id?: string;
22
+ };
23
+ sessionID?: string;
24
+ }
25
+ export default function opencodeNotificationMacOS({ client, directory }: PluginContext): Promise<{
26
+ event: ({ event }: {
27
+ event: Event;
28
+ }) => Promise<void>;
29
+ 'permission.ask': () => Promise<void>;
30
+ 'tool.execute.before': (input: {
31
+ tool: string;
32
+ }) => Promise<void>;
33
+ }>;
34
+ export * from './types.js';
35
+ export * from './config.js';
36
+ export * from './sound.js';
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE;QACN,OAAO,EAAE;YACP,GAAG,EAAE,CAAC,MAAM,EAAE;gBAAE,IAAI,EAAE;oBAAE,EAAE,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,KAAK,OAAO,CAAC;gBAAE,IAAI,CAAC,EAAE;oBAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;SACtF,CAAC;KACH,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAeD,wBAA8B,yBAAyB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,aAAa;uBAK/D;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE;;mCA4BJ;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;GAMxD;AAED,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ import { loadConfig } from './config.js';
2
+ import { handleEvent } from './sound.js';
3
+ function getSessionIDFromEvent(event) {
4
+ return event.data?.sessionID || event.sessionID || event.data?.id || null;
5
+ }
6
+ async function isChildSession(client, sessionID) {
7
+ try {
8
+ const response = await client.session.get({ path: { id: sessionID } });
9
+ return !!response.data?.parentID;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export default async function opencodeNotificationMacOS({ client, directory }) {
16
+ const config = loadConfig();
17
+ const projectName = directory ? directory.split('/').pop() || null : null;
18
+ return {
19
+ event: async ({ event }) => {
20
+ // Permission events
21
+ if (event.type === 'permission.updated' || event.type === 'permission.asked') {
22
+ await handleEvent('permission', projectName, config);
23
+ }
24
+ // Session completion
25
+ if (event.type === 'session.idle') {
26
+ const sessionID = getSessionIDFromEvent(event);
27
+ if (sessionID) {
28
+ const isChild = await isChildSession(client, sessionID);
29
+ const eventType = isChild ? 'subagent_complete' : 'complete';
30
+ await handleEvent(eventType, projectName, config);
31
+ }
32
+ else {
33
+ await handleEvent('complete', projectName, config);
34
+ }
35
+ }
36
+ // Error events
37
+ if (event.type === 'session.error') {
38
+ await handleEvent('error', projectName, config);
39
+ }
40
+ },
41
+ 'permission.ask': async () => {
42
+ await handleEvent('permission', projectName, config);
43
+ },
44
+ 'tool.execute.before': async (input) => {
45
+ if (input.tool === 'question') {
46
+ await handleEvent('question', projectName, config);
47
+ }
48
+ }
49
+ };
50
+ }
51
+ export * from './types.js';
52
+ export * from './config.js';
53
+ export * from './sound.js';
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAqBzC,SAAS,qBAAqB,CAAC,KAAY;IACzC,OAAO,KAAK,CAAC,IAAI,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC;AAC5E,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,MAA+B,EAAE,SAAiB;IAC9E,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QACvE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,yBAAyB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAiB;IAC1F,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAE1E,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAoB,EAAE,EAAE;YAC3C,oBAAoB;YACpB,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAC7E,MAAM,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;YACvD,CAAC;YAED,qBAAqB;YACrB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBAC/C,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;oBACxD,MAAM,SAAS,GAAc,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,UAAU,CAAC;oBACxE,MAAM,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;gBACpD,CAAC;qBAAM,CAAC;oBACN,MAAM,WAAW,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;YAED,eAAe;YACf,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACnC,MAAM,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAED,gBAAgB,EAAE,KAAK,IAAI,EAAE;YAC3B,MAAM,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QAED,qBAAqB,EAAE,KAAK,EAAE,KAAuB,EAAE,EAAE;YACvD,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC9B,MAAM,WAAW,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { SoundConfig, EventType } from './types.js';
2
+ export declare function getSoundPath(soundName: string): string;
3
+ export declare function playSound(soundName: string): Promise<void>;
4
+ export declare function showNotification(title: string, message: string): Promise<void>;
5
+ export declare function handleEvent(eventType: EventType, projectName: string | null, config: {
6
+ sound: boolean;
7
+ notification: boolean;
8
+ showProjectName: boolean;
9
+ messages: Record<EventType, string>;
10
+ sounds: SoundConfig;
11
+ }): Promise<void>;
12
+ export declare function listAvailableSounds(): string[];
13
+ //# sourceMappingURL=sound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sound.d.ts","sourceRoot":"","sources":["../src/sound.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAMpD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACpI,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAM9C"}
package/dist/sound.js ADDED
@@ -0,0 +1,57 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { existsSync } from 'fs';
4
+ import { join } from 'path';
5
+ const execAsync = promisify(exec);
6
+ const SYSTEM_SOUNDS_PATH = '/System/Library/Sounds';
7
+ export function getSoundPath(soundName) {
8
+ // Try system sounds
9
+ const systemPath = join(SYSTEM_SOUNDS_PATH, `${soundName}.aiff`);
10
+ if (existsSync(systemPath)) {
11
+ return systemPath;
12
+ }
13
+ // Fallback to Glass
14
+ return join(SYSTEM_SOUNDS_PATH, 'Glass.aiff');
15
+ }
16
+ export async function playSound(soundName) {
17
+ const soundPath = getSoundPath(soundName);
18
+ try {
19
+ await execAsync(`afplay "${soundPath}"`);
20
+ }
21
+ catch (e) {
22
+ // Silently fail - notifications shouldn't break workflow
23
+ }
24
+ }
25
+ export async function showNotification(title, message) {
26
+ try {
27
+ const escapedTitle = title.replace(/"/g, '\\"');
28
+ const escapedMessage = message.replace(/"/g, '\\"');
29
+ await execAsync(`osascript -e 'display notification "${escapedMessage}" with title "${escapedTitle}"'`);
30
+ }
31
+ catch (e) {
32
+ // Silently fail
33
+ }
34
+ }
35
+ export async function handleEvent(eventType, projectName, config) {
36
+ const title = config.showProjectName && projectName
37
+ ? `OpenCode (${projectName})`
38
+ : 'OpenCode';
39
+ const message = config.messages[eventType];
40
+ // Show notification
41
+ if (config.notification) {
42
+ await showNotification(title, message);
43
+ }
44
+ // Play sound
45
+ if (config.sound) {
46
+ const soundName = config.sounds[eventType];
47
+ await playSound(soundName);
48
+ }
49
+ }
50
+ export function listAvailableSounds() {
51
+ return [
52
+ 'Basso', 'Blow', 'Bottle', 'Frog', 'Funk', 'Glass',
53
+ 'Hero', 'Morse', 'Ping', 'Pop', 'Purr', 'Sosumi',
54
+ 'Submarine', 'Tink'
55
+ ];
56
+ }
57
+ //# sourceMappingURL=sound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sound.js","sourceRoot":"","sources":["../src/sound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAEpD,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,oBAAoB;IACpB,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,oBAAoB;IACpB,OAAO,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAiB;IAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IAE1C,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,WAAW,SAAS,GAAG,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,yDAAyD;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAa,EAAE,OAAe;IACnE,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChD,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACpD,MAAM,SAAS,CAAC,uCAAuC,cAAc,iBAAiB,YAAY,IAAI,CAAC,CAAC;IAC1G,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,gBAAgB;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAoB,EACpB,WAA0B,EAC1B,MAAqI;IAErI,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,IAAI,WAAW;QACjD,CAAC,CAAC,aAAa,WAAW,GAAG;QAC7B,CAAC,CAAC,UAAU,CAAC;IAEf,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAE3C,oBAAoB;IACpB,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,MAAM,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,aAAa;IACb,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;QAClD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ;QAChD,WAAW,EAAE,MAAM;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ export interface SoundConfig {
2
+ complete: string;
3
+ error: string;
4
+ question: string;
5
+ permission: string;
6
+ subagent_complete: string;
7
+ }
8
+ export interface EventConfig {
9
+ sound: boolean;
10
+ notification: boolean;
11
+ }
12
+ export interface PluginConfig {
13
+ sound: boolean;
14
+ notification: boolean;
15
+ showProjectName: boolean;
16
+ events: {
17
+ complete: EventConfig;
18
+ error: EventConfig;
19
+ question: EventConfig;
20
+ permission: EventConfig;
21
+ subagent_complete: EventConfig;
22
+ };
23
+ messages: {
24
+ complete: string;
25
+ error: string;
26
+ question: string;
27
+ permission: string;
28
+ subagent_complete: string;
29
+ };
30
+ sounds: SoundConfig;
31
+ }
32
+ export type EventType = 'complete' | 'error' | 'question' | 'permission' | 'subagent_complete';
33
+ export declare const SYSTEM_SOUNDS: readonly ["Basso", "Blow", "Bottle", "Frog", "Funk", "Glass", "Hero", "Morse", "Ping", "Pop", "Purr", "Sosumi", "Submarine", "Tink"];
34
+ export declare const DEFAULT_CONFIG: PluginConfig;
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE;QACN,QAAQ,EAAE,WAAW,CAAC;QACtB,KAAK,EAAE,WAAW,CAAC;QACnB,QAAQ,EAAE,WAAW,CAAC;QACtB,UAAU,EAAE,WAAW,CAAC;QACxB,iBAAiB,EAAE,WAAW,CAAC;KAChC,CAAC;IACF,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,GAAG,mBAAmB,CAAC;AAE/F,eAAO,MAAM,aAAa,sIAehB,CAAC;AAEX,eAAO,MAAM,cAAc,EAAE,YAyB5B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,43 @@
1
+ export const SYSTEM_SOUNDS = [
2
+ 'Basso',
3
+ 'Blow',
4
+ 'Bottle',
5
+ 'Frog',
6
+ 'Funk',
7
+ 'Glass',
8
+ 'Hero',
9
+ 'Morse',
10
+ 'Ping',
11
+ 'Pop',
12
+ 'Purr',
13
+ 'Sosumi',
14
+ 'Submarine',
15
+ 'Tink'
16
+ ];
17
+ export const DEFAULT_CONFIG = {
18
+ sound: true,
19
+ notification: true,
20
+ showProjectName: true,
21
+ events: {
22
+ complete: { sound: true, notification: true },
23
+ error: { sound: true, notification: true },
24
+ question: { sound: true, notification: true },
25
+ permission: { sound: true, notification: true },
26
+ subagent_complete: { sound: false, notification: false }
27
+ },
28
+ messages: {
29
+ complete: '✅ Task completed',
30
+ error: '❌ Error occurred',
31
+ question: '❓ OpenCode needs your input',
32
+ permission: '🔐 Permission required',
33
+ subagent_complete: 'Subagent finished'
34
+ },
35
+ sounds: {
36
+ complete: 'Glass',
37
+ error: 'Basso',
38
+ question: 'Ping',
39
+ permission: 'Tink',
40
+ subagent_complete: 'Pop'
41
+ }
42
+ };
43
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO;IACP,MAAM;IACN,QAAQ;IACR,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,KAAK;IACL,MAAM;IACN,QAAQ;IACR,WAAW;IACX,MAAM;CACE,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,IAAI;IAClB,eAAe,EAAE,IAAI;IACrB,MAAM,EAAE;QACN,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;QAC7C,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;QAC1C,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;QAC7C,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;QAC/C,iBAAiB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;KACzD;IACD,QAAQ,EAAE;QACR,QAAQ,EAAE,kBAAkB;QAC5B,KAAK,EAAE,kBAAkB;QACzB,QAAQ,EAAE,6BAA6B;QACvC,UAAU,EAAE,wBAAwB;QACpC,iBAAiB,EAAE,mBAAmB;KACvC;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,MAAM;QAChB,UAAU,EAAE,MAAM;QAClB,iBAAiB,EAAE,KAAK;KACzB;CACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "opencode-notification-macos",
3
+ "version": "1.0.0",
4
+ "description": "Native macOS notifications and sounds for OpenCode - zero dependencies",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "opencode-notification-macos": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsc --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "opencode",
28
+ "opencode-plugin",
29
+ "macos",
30
+ "notifications",
31
+ "sound"
32
+ ],
33
+ "author": "Evan Stinger",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/evanstinger/opencode-notification-macos.git"
38
+ },
39
+ "peerDependencies": {
40
+ "@opencode-ai/plugin": ">=1.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@opencode-ai/plugin": "^1.1.0",
44
+ "@types/node": "^22.0.0",
45
+ "@types/inquirer": "^9.0.0",
46
+ "inquirer": "^9.2.0",
47
+ "typescript": "^5.7.0"
48
+ }
49
+ }