opencode-probleemwijken 1.0.1 → 1.2.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 +92 -11
- package/dist/index.js +189 -37
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# opencode-probleemwijken
|
|
2
2
|
|
|
3
|
-
OpenCode plugin dat een willekeurig geluid afspeelt van de legendarische [Probleemwijken/Derkolk soundboard](https://www.derkolk.nl/probleemwijken/) wanneer een sessie klaar is.
|
|
3
|
+
OpenCode plugin dat een willekeurig geluid afspeelt en push notificaties stuurt van de legendarische [Probleemwijken/Derkolk soundboard](https://www.derkolk.nl/probleemwijken/) wanneer een sessie klaar is.
|
|
4
4
|
|
|
5
5
|
## Installatie
|
|
6
6
|
|
|
@@ -24,7 +24,9 @@ Herstart OpenCode en je bent klaar!
|
|
|
24
24
|
|
|
25
25
|
## Wat doet het?
|
|
26
26
|
|
|
27
|
-
Elke keer als OpenCode klaar is met een taak (`session.idle`) of een error krijgt (`session.error`)
|
|
27
|
+
Elke keer als OpenCode klaar is met een taak (`session.idle`) of een error krijgt (`session.error`):
|
|
28
|
+
- Speelt een willekeurig geluid af uit de collectie van 36 klassieke Derkolk soundboard fragmenten
|
|
29
|
+
- Stuurt een push notificatie naar je desktop
|
|
28
30
|
|
|
29
31
|
## Geluiden
|
|
30
32
|
|
|
@@ -42,28 +44,107 @@ Elke keer als OpenCode klaar is met een taak (`session.idle`) of een error krijg
|
|
|
42
44
|
|
|
43
45
|
## Platform ondersteuning
|
|
44
46
|
|
|
45
|
-
| Platform | Audio
|
|
46
|
-
|
|
47
|
-
| macOS | `afplay` (ingebouwd) |
|
|
48
|
-
| Linux | `mpv` of `ffplay` |
|
|
49
|
-
| Windows | Windows Media Player
|
|
47
|
+
| Platform | Audio | Notificaties |
|
|
48
|
+
|----------|-------|--------------|
|
|
49
|
+
| macOS | `afplay` (ingebouwd) | `osascript` (ingebouwd) |
|
|
50
|
+
| Linux | `mpv` of `ffplay` | `notify-send` |
|
|
51
|
+
| Windows | Windows Media Player | Windows Toast Notifications |
|
|
50
52
|
|
|
51
53
|
## Configuratie (optioneel)
|
|
52
54
|
|
|
53
|
-
Maak `~/.config/opencode/probleemwijken.json
|
|
55
|
+
Maak `~/.config/opencode/probleemwijken.json`:
|
|
54
56
|
|
|
55
57
|
```json
|
|
56
58
|
{
|
|
57
59
|
"enabled": true,
|
|
60
|
+
"includeBundledSounds": true,
|
|
61
|
+
"customSoundsDir": null,
|
|
62
|
+
"notifications": {
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"timeout": 5
|
|
65
|
+
},
|
|
66
|
+
"events": {
|
|
67
|
+
"complete": { "sound": true, "notification": true },
|
|
68
|
+
"subagent_complete": { "sound": false, "notification": false },
|
|
69
|
+
"error": { "sound": true, "notification": true },
|
|
70
|
+
"permission": { "sound": false, "notification": false }
|
|
71
|
+
},
|
|
72
|
+
"messages": {
|
|
73
|
+
"complete": "Sessie voltooid!",
|
|
74
|
+
"subagent_complete": "Subagent klaar",
|
|
75
|
+
"error": "Er is een fout opgetreden",
|
|
76
|
+
"permission": "Permissie nodig"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Opties
|
|
82
|
+
|
|
83
|
+
| Optie | Type | Default | Beschrijving |
|
|
84
|
+
|-------|------|---------|--------------|
|
|
85
|
+
| `enabled` | boolean | `true` | Plugin aan/uit |
|
|
86
|
+
| `includeBundledSounds` | boolean | `true` | Probleemwijken geluiden gebruiken |
|
|
87
|
+
| `customSoundsDir` | string | `null` | Pad naar folder met eigen geluiden |
|
|
88
|
+
| `notifications.enabled` | boolean | `true` | Notificaties aan/uit |
|
|
89
|
+
| `notifications.timeout` | number | `5` | Notificatie timeout in seconden (Linux) |
|
|
90
|
+
|
|
91
|
+
### Events
|
|
92
|
+
|
|
93
|
+
Per event kun je sound en notification apart aan/uit zetten:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"events": {
|
|
98
|
+
"complete": { "sound": true, "notification": true },
|
|
99
|
+
"error": { "sound": true, "notification": false }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Of simpelweg een boolean voor beide:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
58
108
|
"events": {
|
|
59
109
|
"complete": true,
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
"error": false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Berichten aanpassen
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"messages": {
|
|
120
|
+
"complete": "Klaar!",
|
|
121
|
+
"error": "Oeps, er ging iets mis"
|
|
63
122
|
}
|
|
64
123
|
}
|
|
65
124
|
```
|
|
66
125
|
|
|
126
|
+
### Eigen geluiden toevoegen
|
|
127
|
+
|
|
128
|
+
1. Maak een folder met je eigen MP3/WAV/OGG bestanden
|
|
129
|
+
2. Configureer het pad in `probleemwijken.json`:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"customSoundsDir": "/home/user/my-sounds"
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
De plugin kiest dan random uit zowel de Probleemwijken geluiden als je eigen geluiden.
|
|
138
|
+
|
|
139
|
+
### Alleen eigen geluiden gebruiken
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"includeBundledSounds": false,
|
|
144
|
+
"customSoundsDir": "/home/user/my-sounds"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
67
148
|
## Credits
|
|
68
149
|
|
|
69
150
|
- Geluiden van [derkolk.nl/probleemwijken](https://www.derkolk.nl/probleemwijken/)
|
package/dist/index.js
CHANGED
|
@@ -4,40 +4,94 @@ import { join } from "path";
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
var DEFAULT_CONFIG = {
|
|
6
6
|
enabled: true,
|
|
7
|
-
|
|
7
|
+
customSoundsDir: null,
|
|
8
|
+
includeBundledSounds: true,
|
|
9
|
+
notifications: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
timeout: 5
|
|
12
|
+
},
|
|
8
13
|
events: {
|
|
9
|
-
complete: true,
|
|
10
|
-
subagent_complete: false,
|
|
11
|
-
error: true,
|
|
12
|
-
permission: false
|
|
14
|
+
complete: { sound: true, notification: true },
|
|
15
|
+
subagent_complete: { sound: false, notification: false },
|
|
16
|
+
error: { sound: true, notification: true },
|
|
17
|
+
permission: { sound: false, notification: false }
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
complete: "Sessie voltooid!",
|
|
21
|
+
subagent_complete: "Subagent klaar",
|
|
22
|
+
error: "Er is een fout opgetreden",
|
|
23
|
+
permission: "Permissie nodig"
|
|
13
24
|
}
|
|
14
25
|
};
|
|
15
26
|
function loadConfig() {
|
|
16
|
-
const
|
|
17
|
-
|
|
27
|
+
const configPaths = [
|
|
28
|
+
join(homedir(), ".config", "opencode", "probleemwijken.json"),
|
|
29
|
+
join(homedir(), ".config", "opencode", "random-soundboard.json")
|
|
30
|
+
];
|
|
31
|
+
let configPath = null;
|
|
32
|
+
for (const path of configPaths) {
|
|
33
|
+
if (existsSync(path)) {
|
|
34
|
+
configPath = path;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!configPath) {
|
|
18
39
|
return DEFAULT_CONFIG;
|
|
19
40
|
}
|
|
20
41
|
try {
|
|
21
42
|
const content = readFileSync(configPath, "utf-8");
|
|
22
43
|
const userConfig = JSON.parse(content);
|
|
44
|
+
const parseEventConfig = (value, defaultValue) => {
|
|
45
|
+
if (typeof value === "boolean") {
|
|
46
|
+
return { sound: value, notification: value };
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "object" && value !== null) {
|
|
49
|
+
return {
|
|
50
|
+
sound: value.sound ?? defaultValue.sound,
|
|
51
|
+
notification: value.notification ?? defaultValue.notification
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return defaultValue;
|
|
55
|
+
};
|
|
23
56
|
return {
|
|
24
57
|
enabled: userConfig.enabled ?? DEFAULT_CONFIG.enabled,
|
|
25
|
-
|
|
58
|
+
customSoundsDir: userConfig.customSoundsDir ?? DEFAULT_CONFIG.customSoundsDir,
|
|
59
|
+
includeBundledSounds: userConfig.includeBundledSounds ?? DEFAULT_CONFIG.includeBundledSounds,
|
|
60
|
+
notifications: {
|
|
61
|
+
enabled: userConfig.notifications?.enabled ?? DEFAULT_CONFIG.notifications.enabled,
|
|
62
|
+
timeout: userConfig.notifications?.timeout ?? DEFAULT_CONFIG.notifications.timeout
|
|
63
|
+
},
|
|
26
64
|
events: {
|
|
27
|
-
complete: userConfig.events?.complete
|
|
28
|
-
subagent_complete: userConfig.events?.subagent_complete
|
|
29
|
-
error: userConfig.events?.error
|
|
30
|
-
permission: userConfig.events?.permission
|
|
65
|
+
complete: parseEventConfig(userConfig.events?.complete, DEFAULT_CONFIG.events.complete),
|
|
66
|
+
subagent_complete: parseEventConfig(userConfig.events?.subagent_complete, DEFAULT_CONFIG.events.subagent_complete),
|
|
67
|
+
error: parseEventConfig(userConfig.events?.error, DEFAULT_CONFIG.events.error),
|
|
68
|
+
permission: parseEventConfig(userConfig.events?.permission, DEFAULT_CONFIG.events.permission)
|
|
69
|
+
},
|
|
70
|
+
messages: {
|
|
71
|
+
complete: userConfig.messages?.complete ?? DEFAULT_CONFIG.messages.complete,
|
|
72
|
+
subagent_complete: userConfig.messages?.subagent_complete ?? DEFAULT_CONFIG.messages.subagent_complete,
|
|
73
|
+
error: userConfig.messages?.error ?? DEFAULT_CONFIG.messages.error,
|
|
74
|
+
permission: userConfig.messages?.permission ?? DEFAULT_CONFIG.messages.permission
|
|
31
75
|
}
|
|
32
76
|
};
|
|
33
77
|
} catch {
|
|
34
78
|
return DEFAULT_CONFIG;
|
|
35
79
|
}
|
|
36
80
|
}
|
|
37
|
-
function
|
|
81
|
+
function isSoundEnabled(config, event) {
|
|
38
82
|
if (!config.enabled)
|
|
39
83
|
return false;
|
|
40
|
-
return config.events[event] ?? false;
|
|
84
|
+
return config.events[event]?.sound ?? false;
|
|
85
|
+
}
|
|
86
|
+
function isNotificationEnabled(config, event) {
|
|
87
|
+
if (!config.enabled)
|
|
88
|
+
return false;
|
|
89
|
+
if (!config.notifications.enabled)
|
|
90
|
+
return false;
|
|
91
|
+
return config.events[event]?.notification ?? false;
|
|
92
|
+
}
|
|
93
|
+
function getMessage(config, event) {
|
|
94
|
+
return config.messages[event] ?? "";
|
|
41
95
|
}
|
|
42
96
|
|
|
43
97
|
// src/sound.ts
|
|
@@ -58,15 +112,9 @@ function getBundledSoundsDir() {
|
|
|
58
112
|
return path;
|
|
59
113
|
}
|
|
60
114
|
}
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
function getSoundsDirectory(config) {
|
|
64
|
-
if (config.soundsDir && existsSync2(config.soundsDir)) {
|
|
65
|
-
return config.soundsDir;
|
|
66
|
-
}
|
|
67
|
-
return getBundledSoundsDir();
|
|
115
|
+
return null;
|
|
68
116
|
}
|
|
69
|
-
function
|
|
117
|
+
function getSoundFilesFromDir(directory) {
|
|
70
118
|
if (!existsSync2(directory)) {
|
|
71
119
|
return [];
|
|
72
120
|
}
|
|
@@ -80,6 +128,19 @@ function getSoundFiles(directory) {
|
|
|
80
128
|
return [];
|
|
81
129
|
}
|
|
82
130
|
}
|
|
131
|
+
function getAllSoundFiles(config) {
|
|
132
|
+
const allSounds = [];
|
|
133
|
+
if (config.includeBundledSounds) {
|
|
134
|
+
const bundledDir = getBundledSoundsDir();
|
|
135
|
+
if (bundledDir) {
|
|
136
|
+
allSounds.push(...getSoundFilesFromDir(bundledDir));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (config.customSoundsDir && existsSync2(config.customSoundsDir)) {
|
|
140
|
+
allSounds.push(...getSoundFilesFromDir(config.customSoundsDir));
|
|
141
|
+
}
|
|
142
|
+
return allSounds;
|
|
143
|
+
}
|
|
83
144
|
function getRandomSound(sounds) {
|
|
84
145
|
if (sounds.length === 0)
|
|
85
146
|
return null;
|
|
@@ -147,8 +208,7 @@ async function playOnWindows(soundPath) {
|
|
|
147
208
|
}
|
|
148
209
|
}
|
|
149
210
|
async function playRandomSound(config) {
|
|
150
|
-
const
|
|
151
|
-
const sounds = getSoundFiles(soundsDir);
|
|
211
|
+
const sounds = getAllSoundFiles(config);
|
|
152
212
|
if (sounds.length === 0) {
|
|
153
213
|
return;
|
|
154
214
|
}
|
|
@@ -174,6 +234,93 @@ async function playRandomSound(config) {
|
|
|
174
234
|
} catch {}
|
|
175
235
|
}
|
|
176
236
|
|
|
237
|
+
// src/notify.ts
|
|
238
|
+
import { platform as platform2 } from "os";
|
|
239
|
+
import { spawn as spawn2 } from "child_process";
|
|
240
|
+
async function runCommand2(command, args) {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
const proc = spawn2(command, args, {
|
|
243
|
+
stdio: "ignore",
|
|
244
|
+
detached: false
|
|
245
|
+
});
|
|
246
|
+
proc.on("error", reject);
|
|
247
|
+
proc.on("close", (code) => {
|
|
248
|
+
if (code === 0)
|
|
249
|
+
resolve();
|
|
250
|
+
else
|
|
251
|
+
reject(new Error(`Exit code ${code}`));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async function notifyMac(title, message) {
|
|
256
|
+
const script = `display notification "${message}" with title "${title}"`;
|
|
257
|
+
await runCommand2("osascript", ["-e", script]);
|
|
258
|
+
}
|
|
259
|
+
async function notifyLinux(title, message, timeout) {
|
|
260
|
+
try {
|
|
261
|
+
await runCommand2("notify-send", ["-t", String(timeout * 1000), title, message]);
|
|
262
|
+
return;
|
|
263
|
+
} catch {
|
|
264
|
+
try {
|
|
265
|
+
await runCommand2("zenity", ["--notification", `--text=${title}: ${message}`]);
|
|
266
|
+
} catch {}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function notifyWindows(title, message) {
|
|
270
|
+
const script = `
|
|
271
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
272
|
+
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
|
|
273
|
+
$template = @"
|
|
274
|
+
<toast>
|
|
275
|
+
<visual>
|
|
276
|
+
<binding template="ToastText02">
|
|
277
|
+
<text id="1">$($args[0])</text>
|
|
278
|
+
<text id="2">$($args[1])</text>
|
|
279
|
+
</binding>
|
|
280
|
+
</visual>
|
|
281
|
+
</toast>
|
|
282
|
+
"@
|
|
283
|
+
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
284
|
+
$xml.LoadXml($template)
|
|
285
|
+
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
|
|
286
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("OpenCode").Show($toast)
|
|
287
|
+
`;
|
|
288
|
+
try {
|
|
289
|
+
await runCommand2("powershell", ["-c", script, title, message]);
|
|
290
|
+
} catch {
|
|
291
|
+
const simpleScript = `
|
|
292
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
293
|
+
$balloon = New-Object System.Windows.Forms.NotifyIcon
|
|
294
|
+
$balloon.Icon = [System.Drawing.SystemIcons]::Information
|
|
295
|
+
$balloon.BalloonTipTitle = $args[0]
|
|
296
|
+
$balloon.BalloonTipText = $args[1]
|
|
297
|
+
$balloon.Visible = $true
|
|
298
|
+
$balloon.ShowBalloonTip(5000)
|
|
299
|
+
Start-Sleep -Seconds 1
|
|
300
|
+
$balloon.Dispose()
|
|
301
|
+
`;
|
|
302
|
+
try {
|
|
303
|
+
await runCommand2("powershell", ["-c", simpleScript, title, message]);
|
|
304
|
+
} catch {}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function sendNotification(title, message, timeout = 5) {
|
|
308
|
+
const os = platform2();
|
|
309
|
+
try {
|
|
310
|
+
switch (os) {
|
|
311
|
+
case "darwin":
|
|
312
|
+
await notifyMac(title, message);
|
|
313
|
+
break;
|
|
314
|
+
case "linux":
|
|
315
|
+
await notifyLinux(title, message, timeout);
|
|
316
|
+
break;
|
|
317
|
+
case "win32":
|
|
318
|
+
await notifyWindows(title, message);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
|
|
177
324
|
// src/index.ts
|
|
178
325
|
function getSessionIDFromEvent(event) {
|
|
179
326
|
const sessionID = event?.properties?.sessionID;
|
|
@@ -191,33 +338,38 @@ async function isChildSession(client, sessionID) {
|
|
|
191
338
|
return false;
|
|
192
339
|
}
|
|
193
340
|
}
|
|
194
|
-
|
|
341
|
+
async function handleEvent(config, eventType, projectName) {
|
|
342
|
+
const promises = [];
|
|
343
|
+
if (isSoundEnabled(config, eventType)) {
|
|
344
|
+
promises.push(playRandomSound(config));
|
|
345
|
+
}
|
|
346
|
+
if (isNotificationEnabled(config, eventType)) {
|
|
347
|
+
const title = projectName ? `OpenCode (${projectName})` : "OpenCode";
|
|
348
|
+
const message = getMessage(config, eventType);
|
|
349
|
+
promises.push(sendNotification(title, message, config.notifications.timeout));
|
|
350
|
+
}
|
|
351
|
+
await Promise.allSettled(promises);
|
|
352
|
+
}
|
|
353
|
+
var RandomSoundboardPlugin = async ({ client, directory }) => {
|
|
195
354
|
const config = loadConfig();
|
|
355
|
+
const projectName = directory ? directory.split("/").pop() ?? null : null;
|
|
196
356
|
return {
|
|
197
357
|
event: async ({ event }) => {
|
|
198
358
|
if (event.type === "permission.updated" || event.type === "permission.asked") {
|
|
199
|
-
|
|
200
|
-
await playRandomSound(config);
|
|
201
|
-
}
|
|
359
|
+
await handleEvent(config, "permission", projectName);
|
|
202
360
|
}
|
|
203
361
|
if (event.type === "session.idle") {
|
|
204
362
|
const sessionID = getSessionIDFromEvent(event);
|
|
205
363
|
if (sessionID) {
|
|
206
364
|
const isChild = await isChildSession(client, sessionID);
|
|
207
365
|
const eventType = isChild ? "subagent_complete" : "complete";
|
|
208
|
-
|
|
209
|
-
await playRandomSound(config);
|
|
210
|
-
}
|
|
366
|
+
await handleEvent(config, eventType, projectName);
|
|
211
367
|
} else {
|
|
212
|
-
|
|
213
|
-
await playRandomSound(config);
|
|
214
|
-
}
|
|
368
|
+
await handleEvent(config, "complete", projectName);
|
|
215
369
|
}
|
|
216
370
|
}
|
|
217
371
|
if (event.type === "session.error") {
|
|
218
|
-
|
|
219
|
-
await playRandomSound(config);
|
|
220
|
-
}
|
|
372
|
+
await handleEvent(config, "error", projectName);
|
|
221
373
|
}
|
|
222
374
|
}
|
|
223
375
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-probleemwijken",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "OpenCode plugin that plays
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "OpenCode plugin that plays random Probleemwijken/Derkolk sounds and sends push notifications when a session completes",
|
|
5
5
|
"author": "Daan-Friday",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|