opencode-plugin-boops 2.0.0 → 2.1.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
@@ -13,19 +13,27 @@ Sound notifications for OpenCode - plays pleasant "boop" sounds when tasks compl
13
13
 
14
14
  ## Installation
15
15
 
16
- Add to your OpenCode config file:
16
+ ### Quick Install (Recommended)
17
17
 
18
- **Global installation** (recommended):
19
18
  ```bash
20
- # Edit ~/.config/opencode/opencode.json
19
+ npx opencode-plugin-boops install
21
20
  ```
22
21
 
23
- **Per-project installation**:
24
- ```bash
25
- # Edit opencode.json in your project root
22
+ This automatically adds the plugin to your OpenCode config. Then restart OpenCode.
23
+
24
+ ### Manual Installation
25
+
26
+ Add the plugin to your OpenCode config file:
27
+
28
+ **Global** (`~/.config/opencode/opencode.json`):
29
+ ```json
30
+ {
31
+ "$schema": "https://opencode.ai/config.json",
32
+ "plugin": ["opencode-plugin-boops"]
33
+ }
26
34
  ```
27
35
 
28
- Add the plugin to the config:
36
+ **Per-project** (`opencode.json` in project root):
29
37
  ```json
30
38
  {
31
39
  "$schema": "https://opencode.ai/config.json",
@@ -35,21 +43,21 @@ Add the plugin to the config:
35
43
 
36
44
  Then restart OpenCode. The plugin will be automatically downloaded and installed from npm.
37
45
 
38
- ### Quick setup
39
-
40
- If you don't have an OpenCode config yet:
46
+ ## CLI Commands
41
47
 
42
48
  ```bash
43
- # Create global config
44
- mkdir -p ~/.config/opencode
45
- echo '{"$schema":"https://opencode.ai/config.json","plugin":["opencode-plugin-boops"]}' > ~/.config/opencode/opencode.json
46
- ```
49
+ # Install plugin (adds to OpenCode config)
50
+ npx opencode-plugin-boops install
47
51
 
48
- Or for a specific project:
52
+ # Uninstall plugin (removes from config)
53
+ npx opencode-plugin-boops uninstall # Interactive - asks about data cleanup
54
+ npx opencode-plugin-boops uninstall --full # Remove plugin + data
49
55
 
50
- ```bash
51
- # In your project directory
52
- echo '{"$schema":"https://opencode.ai/config.json","plugin":["opencode-plugin-boops"]}' > opencode.json
56
+ # Browse 448 sounds with semantic tags
57
+ npx opencode-plugin-boops browse
58
+
59
+ # Show help
60
+ npx opencode-plugin-boops
53
61
  ```
54
62
 
55
63
  ## How it works
@@ -64,30 +72,27 @@ Sounds are downloaded once on first use and cached in `~/.cache/opencode/boops/`
64
72
 
65
73
  ## Configuration
66
74
 
67
- The plugin uses a TOML configuration file located at `~/.config/opencode/boops.toml`.
75
+ The plugin automatically creates a configuration file at `~/.config/opencode/plugins/boops/boops.toml` on first install.
68
76
 
69
- ### Create your config
77
+ ### Customize your config
70
78
 
71
- Copy the default configuration:
79
+ The config is automatically created from `boops.default.toml` when you install the plugin. Edit it to customize your sounds:
72
80
 
73
81
  ```bash
74
- # Get the default config template
75
- curl -o ~/.config/opencode/boops.toml https://raw.githubusercontent.com/towc/opencode-plugin-boops/main/boops.default.toml
82
+ # Edit your config
83
+ $EDITOR ~/.config/opencode/plugins/boops/boops.toml
76
84
  ```
77
85
 
78
- Or create it manually:
86
+ Example configuration:
79
87
 
80
88
  ```toml
81
- # ~/.config/opencode/boops.toml
89
+ # ~/.config/opencode/plugins/boops/boops.toml
82
90
 
83
91
  [sounds]
84
- # Simple: Use notificationsounds.com IDs (recommended)
85
- "session.idle" = "1150-pristine"
86
- "permission.asked" = "1217-relax"
87
- "session.error" = "1219-magic"
88
-
89
- # Or search by name (finds first match):
90
- # "session.idle" = "pristine"
92
+ # Use sound names from sounds.json (recommended)
93
+ "session.idle" = "pristine"
94
+ "permission.asked" = "relax"
95
+ "session.error" = "magic"
91
96
 
92
97
  # Or use full URLs:
93
98
  # "session.idle" = "https://example.com/sound.ogg"
@@ -100,13 +105,14 @@ Or create it manually:
100
105
 
101
106
  The plugin supports multiple ways to specify sounds:
102
107
 
103
- **1. notificationsounds.com IDs (easiest):**
108
+ **1. Sound names from sounds.json (easiest):**
104
109
  ```toml
105
- "session.idle" = "1150-pristine" # Direct ID (fast)
106
- "session.idle" = "pristine" # Search by name (slower, first match)
110
+ "session.idle" = "pristine"
111
+ "permission.asked" = "relax"
112
+ "session.error" = "magic"
107
113
  ```
108
114
 
109
- Browse sounds at [notificationsounds.com](https://notificationsounds.com/notification-sounds) to find IDs.
115
+ Use the TUI browser (`~/.config/opencode/plugins/boops/browse`) to explore all 448 sounds with semantic tags!
110
116
 
111
117
  **2. Full URLs:**
112
118
  ```toml
@@ -177,7 +183,7 @@ For advanced use cases, you can add filters to play sounds only when certain con
177
183
 
178
184
  ```toml
179
185
  [sounds.session.idle]
180
- sound = "https://notificationsounds.com/storage/sounds/file-sounds-1150-pristine.ogg"
186
+ sound = "pristine"
181
187
  not_if = { agent = "explore" } # Skip subagent completions
182
188
  ```
183
189
 
@@ -208,8 +214,9 @@ You can test sounds without restarting OpenCode using the custom tool:
208
214
  test-sound session.idle
209
215
 
210
216
  # Test a sound ID directly
217
+ # Test by sound name
211
218
  test-sound pristine
212
- test-sound 1150-pristine
219
+ test-sound "access granted computer voice"
213
220
 
214
221
  # Test a URL directly
215
222
  test-sound https://example.com/sound.ogg
@@ -231,11 +238,11 @@ The test command automatically reloads your config file, so you can edit `boops.
231
238
  The plugin includes a beautiful TUI for browsing and assigning sounds:
232
239
 
233
240
  ```bash
234
- # If plugin is installed
235
- ~/.config/opencode/plugins/boops/browse
236
-
237
- # Or try it with npx (browse-only, saving disabled)
241
+ # Browse and test sounds
238
242
  npx opencode-plugin-boops browse
243
+
244
+ # Or after installation:
245
+ ~/.config/opencode/plugins/boops/browse
239
246
  ```
240
247
 
241
248
  Features:
@@ -4,20 +4,19 @@
4
4
  # player = "paplay" # Auto-detected if not set (paplay/aplay/afplay)
5
5
 
6
6
  [sounds]
7
- "session.idle" = "1150-pristine" # AI completes response
8
- "permission.asked" = "1217-relax" # AI needs permission
9
- "session.error" = "1219-magic" # Error occurs
7
+ "session.idle" = "pristine" # AI completes response
8
+ "permission.asked" = "relax" # AI needs permission
9
+ "session.error" = "magic" # Error occurs
10
10
 
11
11
  # You can use:
12
- # - Sound IDs: "pristine" (searches notificationsounds.com)
13
- # - Numeric IDs: "1150-pristine" (direct match, faster)
12
+ # - Sound names: "pristine" (searches sounds.json by name)
14
13
  # - Full URLs: "https://example.com/sound.ogg"
15
14
  # - Local paths: "/usr/share/sounds/..."
16
15
 
17
16
  # Advanced: Event filters (play sound only when conditions match)
18
17
  # Uncomment and adjust after checking logs to see available properties
19
18
  # [sounds.session.idle]
20
- # sound = "1150-pristine"
19
+ # sound = "pristine"
21
20
  # not_if = { agent = "explore" } # Don't play for subagent completions
22
21
 
23
22
  # Optional events (uncomment to enable):
package/cli/browse CHANGED
@@ -58,7 +58,7 @@ const PLUGIN_INSTALLED = isPluginInstalled();
58
58
 
59
59
  // Load current boops.toml config
60
60
  function loadCurrentConfig() {
61
- const configPath = join(homedir(), ".config", "opencode", "boops.toml");
61
+ const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml");
62
62
  if (!existsSync(configPath)) {
63
63
  return {};
64
64
  }
@@ -105,7 +105,7 @@ function saveConfig(config) {
105
105
  return false;
106
106
  }
107
107
 
108
- const configPath = join(homedir(), ".config", "opencode", "boops.toml");
108
+ const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml");
109
109
  let content = "# OpenCode Boops Plugin Configuration\n\n[sounds]\n";
110
110
 
111
111
  if (config.sounds) {
package/cli/install ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenCode Boops Plugin - Installer
4
+ * Automatically adds the plugin to your OpenCode config
5
+ */
6
+
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+
11
+ const CONFIG_DIR = join(homedir(), ".config", "opencode");
12
+ const CONFIG_PATH = join(CONFIG_DIR, "opencode.json");
13
+ const PLUGIN_NAME = "opencode-plugin-boops";
14
+
15
+ console.log("šŸ”Š OpenCode Boops Plugin Installer\n");
16
+
17
+ // Ensure config directory exists
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+
20
+ // Read or create config
21
+ let config;
22
+ if (existsSync(CONFIG_PATH)) {
23
+ console.log("šŸ“ Found existing OpenCode config");
24
+ try {
25
+ const content = readFileSync(CONFIG_PATH, "utf-8");
26
+ config = JSON.parse(content);
27
+ } catch (error) {
28
+ console.error("āŒ Failed to parse opencode.json:", error.message);
29
+ console.log("Please fix your config manually at:", CONFIG_PATH);
30
+ process.exit(1);
31
+ }
32
+ } else {
33
+ console.log("šŸ“ Creating new OpenCode config");
34
+ config = {
35
+ "$schema": "https://opencode.ai/config.json",
36
+ };
37
+ }
38
+
39
+ // Add plugin if not already present
40
+ if (!config.plugin) {
41
+ config.plugin = [];
42
+ } else if (typeof config.plugin === "string") {
43
+ config.plugin = [config.plugin];
44
+ }
45
+
46
+ if (config.plugin.includes(PLUGIN_NAME)) {
47
+ console.log("āœ“ Plugin already installed in config");
48
+ } else {
49
+ config.plugin.push(PLUGIN_NAME);
50
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
51
+ console.log("āœ“ Added plugin to OpenCode config");
52
+ }
53
+
54
+ console.log("\nšŸ“ Config location:", CONFIG_PATH);
55
+ console.log("\nšŸŽµ Next steps:");
56
+ console.log(" 1. Restart OpenCode (the plugin will auto-install)");
57
+ console.log(" 2. Browse sounds: npx opencode-plugin-boops browse");
58
+ console.log(" 3. Customize config: $EDITOR ~/.config/opencode/plugins/boops/boops.toml");
59
+ console.log("\n✨ Done!");
package/cli/main ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenCode Boops Plugin - CLI Entry Point
4
+ */
5
+
6
+ import { spawn } from "child_process";
7
+ import { fileURLToPath } from "url";
8
+ import { dirname, join } from "path";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ const command = process.argv[2] || "browse";
14
+ const args = process.argv.slice(3);
15
+
16
+ const commands = {
17
+ install: join(__dirname, "install"),
18
+ uninstall: join(__dirname, "uninstall"),
19
+ browse: join(__dirname, "browse"),
20
+ };
21
+
22
+ if (!commands[command]) {
23
+ console.log("OpenCode Boops Plugin\n");
24
+ console.log("Usage:");
25
+ console.log(" npx opencode-plugin-boops install - Add plugin to OpenCode config");
26
+ console.log(" npx opencode-plugin-boops uninstall - Remove plugin from config");
27
+ console.log(" npx opencode-plugin-boops browse - Browse and test sounds (default)");
28
+ console.log(" npx opencode-plugin-boops - Same as browse");
29
+ process.exit(1);
30
+ }
31
+
32
+ const child = spawn(commands[command], args, {
33
+ stdio: "inherit",
34
+ });
35
+
36
+ child.on("exit", (code) => {
37
+ process.exit(code || 0);
38
+ });
package/cli/uninstall ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenCode Boops Plugin - Uninstaller
4
+ * Removes the plugin from OpenCode config and optionally cleans up data
5
+ */
6
+
7
+ import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ import { createInterface } from "readline";
11
+
12
+ const CONFIG_DIR = join(homedir(), ".config", "opencode");
13
+ const CONFIG_PATH = join(CONFIG_DIR, "opencode.json");
14
+ const PLUGIN_NAME = "opencode-plugin-boops";
15
+ const PLUGIN_DIR = join(CONFIG_DIR, "plugins", "boops");
16
+ const CACHE_DIR = join(homedir(), ".cache", "opencode", "boops");
17
+
18
+ console.log("šŸ”Š OpenCode Boops Plugin Uninstaller\n");
19
+
20
+ // Remove from config
21
+ if (existsSync(CONFIG_PATH)) {
22
+ try {
23
+ const content = readFileSync(CONFIG_PATH, "utf-8");
24
+ const config = JSON.parse(content);
25
+
26
+ if (config.plugin) {
27
+ if (typeof config.plugin === "string") {
28
+ if (config.plugin === PLUGIN_NAME) {
29
+ delete config.plugin;
30
+ console.log("āœ“ Removed plugin from config");
31
+ } else {
32
+ console.log("ℹ Plugin not found in config");
33
+ }
34
+ } else if (Array.isArray(config.plugin)) {
35
+ const index = config.plugin.indexOf(PLUGIN_NAME);
36
+ if (index > -1) {
37
+ config.plugin.splice(index, 1);
38
+ if (config.plugin.length === 0) {
39
+ delete config.plugin;
40
+ }
41
+ console.log("āœ“ Removed plugin from config");
42
+ } else {
43
+ console.log("ℹ Plugin not found in config");
44
+ }
45
+ }
46
+
47
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
48
+ } else {
49
+ console.log("ℹ No plugins in config");
50
+ }
51
+ } catch (error) {
52
+ console.error("āŒ Failed to update config:", error.message);
53
+ process.exit(1);
54
+ }
55
+ } else {
56
+ console.log("ℹ No OpenCode config found");
57
+ }
58
+
59
+ // Ask about cleaning up data
60
+ const rl = createInterface({
61
+ input: process.stdin,
62
+ output: process.stdout,
63
+ });
64
+
65
+ const shouldCleanup = process.argv.includes("--full") || process.argv.includes("-f");
66
+
67
+ if (shouldCleanup) {
68
+ cleanup();
69
+ } else {
70
+ console.log("\nšŸ“¦ Plugin data locations:");
71
+ if (existsSync(PLUGIN_DIR)) {
72
+ console.log(" • Config & data:", PLUGIN_DIR);
73
+ }
74
+ if (existsSync(CACHE_DIR)) {
75
+ console.log(" • Cached sounds:", CACHE_DIR);
76
+ }
77
+
78
+ console.log("\nšŸ—‘ļø Clean up plugin data?");
79
+ console.log(" This will remove your custom config and cached sounds.");
80
+
81
+ rl.question("\n Remove data? [y/N] ", (answer) => {
82
+ if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
83
+ cleanup();
84
+ } else {
85
+ console.log("\nāœ“ Kept plugin data (you can remove manually later)");
86
+ console.log(" rm -rf", PLUGIN_DIR);
87
+ console.log(" rm -rf", CACHE_DIR);
88
+ }
89
+ rl.close();
90
+ });
91
+ }
92
+
93
+ function cleanup() {
94
+ let removed = false;
95
+
96
+ if (existsSync(PLUGIN_DIR)) {
97
+ rmSync(PLUGIN_DIR, { recursive: true, force: true });
98
+ console.log("āœ“ Removed plugin directory");
99
+ removed = true;
100
+ }
101
+
102
+ if (existsSync(CACHE_DIR)) {
103
+ rmSync(CACHE_DIR, { recursive: true, force: true });
104
+ console.log("āœ“ Removed cached sounds");
105
+ removed = true;
106
+ }
107
+
108
+ if (!removed) {
109
+ console.log("ℹ No plugin data found");
110
+ }
111
+
112
+ console.log("\n✨ Uninstall complete!");
113
+ console.log("\nšŸ’” Restart OpenCode to complete the removal");
114
+
115
+ if (!shouldCleanup) {
116
+ rl.close();
117
+ }
118
+ }
package/index.ts CHANGED
@@ -62,7 +62,7 @@ export const BoopsPlugin: Plugin = async ({ client }) => {
62
62
  // Directory might already exist
63
63
  }
64
64
 
65
- const configPath = join(homedir(), ".config", "opencode", "boops.toml")
65
+ const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml")
66
66
 
67
67
  // Load configuration (can be called multiple times to reload)
68
68
  const loadConfig = async (): Promise<BoopsConfig> => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-boops",
3
- "version": "2.0.0",
3
+ "version": "2.1.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",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "homepage": "https://github.com/towc/opencode-plugin-boops#readme",
25
25
  "bin": {
26
- "opencode-plugin-boops": "cli/browse"
26
+ "opencode-plugin-boops": "cli/main"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@opencode-ai/plugin": "^1.0.0"
@@ -33,12 +33,15 @@
33
33
  "smol-toml": "^1.3.1"
34
34
  },
35
35
  "scripts": {
36
- "postinstall": "node -e \"const fs=require('fs'),p=require('path'),h=require('os').homedir(),d=p.join(h,'.config','opencode','plugins','boops');fs.mkdirSync(d,{recursive:true});fs.copyFileSync(p.join(__dirname,'cli','browse'),p.join(d,'browse'));fs.copyFileSync(p.join(__dirname,'sounds.json'),p.join(d,'sounds.json'));fs.chmodSync(p.join(d,'browse'),0o755);console.log('āœ“ Installed browse CLI to ~/.config/opencode/plugins/boops/browse')\""
36
+ "postinstall": "node -e \"const fs=require('fs'),p=require('path'),h=require('os').homedir(),d=p.join(h,'.config','opencode','plugins','boops');fs.mkdirSync(d,{recursive:true});const cfg=p.join(d,'boops.toml');if(!fs.existsSync(cfg))fs.copyFileSync(p.join(__dirname,'boops.default.toml'),cfg);fs.copyFileSync(p.join(__dirname,'cli','browse'),p.join(d,'browse'));fs.copyFileSync(p.join(__dirname,'sounds.json'),p.join(d,'sounds.json'));fs.chmodSync(p.join(d,'browse'),0o755);console.log('āœ“ Installed to ~/.config/opencode/plugins/boops/')\""
37
37
  },
38
38
  "files": [
39
39
  "index.ts",
40
40
  "sounds.json",
41
41
  "boops.default.toml",
42
+ "cli/main",
43
+ "cli/install",
44
+ "cli/uninstall",
42
45
  "cli/browse",
43
46
  "README.md",
44
47
  "LICENSE"