opencode-plugin-boops 2.3.0 → 2.5.1

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 CHANGED
@@ -69,6 +69,8 @@ The plugin listens to OpenCode events and plays sounds based on your configurati
69
69
  - **`permission.asked`** - AI needs permission → gentle "relax" chime
70
70
  - **`session.error`** - An error occurs → friendly "magic" alert
71
71
 
72
+ **Instant config reload:** The config is reloaded on every event, so changes take effect immediately without restarting OpenCode! Edit your config, trigger an event, and hear the new sound instantly.
73
+
72
74
  Sounds are downloaded once on first use and cached in `~/.cache/opencode/boops/` for instant playback.
73
75
 
74
76
  ## Configuration
package/cli/browse CHANGED
@@ -127,7 +127,7 @@ function loadCurrentConfig() {
127
127
  }
128
128
  }
129
129
 
130
- // Save config to boops.toml (preserves existing entries and comments)
130
+ // Save config to boops.toml
131
131
  function saveConfig(config) {
132
132
  if (!PLUGIN_INSTALLED) {
133
133
  console.error("\n⚠️ Cannot save: Plugin not installed");
@@ -138,48 +138,14 @@ function saveConfig(config) {
138
138
 
139
139
  const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml");
140
140
 
141
- // Read existing config to preserve comments and other sections
142
- let existingContent = "";
143
- if (existsSync(configPath)) {
144
- existingContent = readFileSync(configPath, "utf-8");
145
- }
146
-
147
- // Parse existing [sounds] section to merge with new values
148
- const existingSounds = {};
149
- if (existingContent) {
150
- const lines = existingContent.split("\n");
151
- let inSoundsSection = false;
152
-
153
- for (const line of lines) {
154
- const trimmed = line.trim();
155
-
156
- if (trimmed === "[sounds]") {
157
- inSoundsSection = true;
158
- continue;
159
- }
160
-
161
- if (trimmed.startsWith("[") && trimmed !== "[sounds]") {
162
- inSoundsSection = false;
163
- }
164
-
165
- if (inSoundsSection && !trimmed.startsWith("#") && trimmed) {
166
- const match = trimmed.match(/^"?([^"=]+)"?\s*=\s*"?([^"#]+)"?/);
167
- if (match) {
168
- const [, key, value] = match;
169
- existingSounds[key.trim()] = value.trim().replace(/"/g, "");
170
- }
171
- }
172
- }
173
- }
174
-
175
- // Merge new sounds with existing
176
- const mergedSounds = { ...existingSounds, ...(config.sounds || {}) };
177
-
178
- // Write back, preserving the header
141
+ // Write config as-is (don't merge with file, since config was loaded from file)
142
+ // This allows deletions to work properly
179
143
  let content = "# OpenCode Boops Plugin Configuration\n# Sounds can be local file paths OR URLs (automatically downloaded and cached)\n\n[sounds]\n";
180
144
 
181
- for (const [event, sound] of Object.entries(mergedSounds).sort()) {
182
- content += `"${event}" = "${sound}"\n`;
145
+ if (config.sounds) {
146
+ for (const [event, sound] of Object.entries(config.sounds).sort()) {
147
+ content += `"${event}" = "${sound}"\n`;
148
+ }
183
149
  }
184
150
 
185
151
  writeFileSync(configPath, content);
@@ -1274,7 +1240,7 @@ async function browse() {
1274
1240
  let controls;
1275
1241
  if (state.pickerMode) {
1276
1242
  controls = PLUGIN_INSTALLED
1277
- ? ' \x1b[38;5;240m↑/↓ select enter play ctrl+s save d unassign ←/esc close\x1b[0m'
1243
+ ? ' \x1b[38;5;240m↑/↓ select enter play a assign d unassign ←/esc close\x1b[0m'
1278
1244
  : ' \x1b[38;5;240m↑/↓ select event enter play ←/esc close \x1b[38;5;208m(save disabled)\x1b[0m';
1279
1245
  } else {
1280
1246
  controls = ' \x1b[38;5;240m↑/↓ navigate ←/→ tags / search s assign enter play q quit\x1b[0m';
@@ -1925,18 +1891,18 @@ async function browse() {
1925
1891
  }
1926
1892
  }
1927
1893
  return;
1928
- } else if (key === '\u0013') {
1929
- // Ctrl+S - save assignment (stay in picker mode to assign more)
1894
+ } else if (key === 'a') {
1895
+ // 'a' - assign sound to selected event (auto-saves)
1930
1896
  const config = loadCurrentConfig();
1931
1897
  const event = availableEvents[state.pickerSelectedEvent];
1932
1898
 
1933
1899
  if (!config.sounds) config.sounds = {};
1934
1900
  config.sounds[event] = state.pickerSound.name; // Save friendly name, not ID
1935
1901
 
1936
- // Save to config file
1902
+ // Auto-save to config file
1937
1903
  saveConfig(config);
1938
1904
 
1939
- // Stay in picker mode, just show it was saved
1905
+ // Stay in picker mode to assign more
1940
1906
  render();
1941
1907
  return;
1942
1908
  } else if (key === 'd') {
package/index.ts CHANGED
@@ -68,9 +68,9 @@ export const BoopsPlugin: Plugin = async ({ client }) => {
68
68
  const loadConfig = async (): Promise<BoopsConfig> => {
69
69
  let config: BoopsConfig = {
70
70
  sounds: {
71
- "session.idle": "1150-pristine",
72
- "permission.asked": "1217-relax",
73
- "session.error": "1219-magic",
71
+ "session.idle": "pristine",
72
+ "permission.asked": "relax",
73
+ "session.error": "magic",
74
74
  },
75
75
  fallbacks: {
76
76
  default: "/usr/share/sounds/alsa/Front_Center.wav"
@@ -96,10 +96,13 @@ export const BoopsPlugin: Plugin = async ({ client }) => {
96
96
  return config
97
97
  }
98
98
 
99
+ // Load initial config (will be reloaded on every event for instant updates)
99
100
  let config = await loadConfig()
100
101
 
101
102
  // Detect available sound player
102
103
  const detectPlayer = async (): Promise<string> => {
104
+ // Reload config to check for player changes
105
+ config = await loadConfig()
103
106
  if (config.player) return config.player
104
107
 
105
108
  const players = ["paplay", "aplay", "afplay"]
@@ -306,10 +309,14 @@ export const BoopsPlugin: Plugin = async ({ client }) => {
306
309
 
307
310
  return {
308
311
  event: async ({ event }) => {
312
+ // Reload config on every event for instant updates (no restart needed!)
313
+ config = await loadConfig()
314
+
309
315
  // Log session.idle events for debugging (helpful for users configuring filters)
310
316
  if (event.type === "session.idle") {
311
317
  await log(`session.idle event received`, {
312
- properties: event
318
+ properties: event,
319
+ configuredSound: config.sounds["session.idle"]
313
320
  })
314
321
  }
315
322
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-boops",
3
- "version": "2.3.0",
3
+ "version": "2.5.1",
4
4
  "description": "Sound notifications for OpenCode - plays pleasant sounds when tasks complete or input is needed",
5
5
  "main": "index.ts",
6
6
  "type": "module",