opencode-probleemwijken 1.1.0 → 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 +61 -16
- package/dist/index.js +157 -23
- 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,11 +44,11 @@ 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
|
|
|
@@ -56,12 +58,22 @@ Maak `~/.config/opencode/probleemwijken.json`:
|
|
|
56
58
|
{
|
|
57
59
|
"enabled": true,
|
|
58
60
|
"includeBundledSounds": true,
|
|
59
|
-
"customSoundsDir":
|
|
61
|
+
"customSoundsDir": null,
|
|
62
|
+
"notifications": {
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"timeout": 5
|
|
65
|
+
},
|
|
60
66
|
"events": {
|
|
61
|
-
"complete": true,
|
|
62
|
-
"subagent_complete": false,
|
|
63
|
-
"error": true,
|
|
64
|
-
"permission": false
|
|
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"
|
|
65
77
|
}
|
|
66
78
|
}
|
|
67
79
|
```
|
|
@@ -73,10 +85,43 @@ Maak `~/.config/opencode/probleemwijken.json`:
|
|
|
73
85
|
| `enabled` | boolean | `true` | Plugin aan/uit |
|
|
74
86
|
| `includeBundledSounds` | boolean | `true` | Probleemwijken geluiden gebruiken |
|
|
75
87
|
| `customSoundsDir` | string | `null` | Pad naar folder met eigen geluiden |
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
{
|
|
108
|
+
"events": {
|
|
109
|
+
"complete": true,
|
|
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"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
80
125
|
|
|
81
126
|
### Eigen geluiden toevoegen
|
|
82
127
|
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,21 @@ var DEFAULT_CONFIG = {
|
|
|
6
6
|
enabled: true,
|
|
7
7
|
customSoundsDir: null,
|
|
8
8
|
includeBundledSounds: true,
|
|
9
|
+
notifications: {
|
|
10
|
+
enabled: true,
|
|
11
|
+
timeout: 5
|
|
12
|
+
},
|
|
9
13
|
events: {
|
|
10
|
-
complete: true,
|
|
11
|
-
subagent_complete: false,
|
|
12
|
-
error: true,
|
|
13
|
-
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"
|
|
14
24
|
}
|
|
15
25
|
};
|
|
16
26
|
function loadConfig() {
|
|
@@ -31,25 +41,57 @@ function loadConfig() {
|
|
|
31
41
|
try {
|
|
32
42
|
const content = readFileSync(configPath, "utf-8");
|
|
33
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
|
+
};
|
|
34
56
|
return {
|
|
35
57
|
enabled: userConfig.enabled ?? DEFAULT_CONFIG.enabled,
|
|
36
58
|
customSoundsDir: userConfig.customSoundsDir ?? DEFAULT_CONFIG.customSoundsDir,
|
|
37
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
|
+
},
|
|
38
64
|
events: {
|
|
39
|
-
complete: userConfig.events?.complete
|
|
40
|
-
subagent_complete: userConfig.events?.subagent_complete
|
|
41
|
-
error: userConfig.events?.error
|
|
42
|
-
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
|
|
43
75
|
}
|
|
44
76
|
};
|
|
45
77
|
} catch {
|
|
46
78
|
return DEFAULT_CONFIG;
|
|
47
79
|
}
|
|
48
80
|
}
|
|
49
|
-
function
|
|
81
|
+
function isSoundEnabled(config, event) {
|
|
82
|
+
if (!config.enabled)
|
|
83
|
+
return false;
|
|
84
|
+
return config.events[event]?.sound ?? false;
|
|
85
|
+
}
|
|
86
|
+
function isNotificationEnabled(config, event) {
|
|
50
87
|
if (!config.enabled)
|
|
51
88
|
return false;
|
|
52
|
-
|
|
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] ?? "";
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
// src/sound.ts
|
|
@@ -192,6 +234,93 @@ async function playRandomSound(config) {
|
|
|
192
234
|
} catch {}
|
|
193
235
|
}
|
|
194
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
|
+
|
|
195
324
|
// src/index.ts
|
|
196
325
|
function getSessionIDFromEvent(event) {
|
|
197
326
|
const sessionID = event?.properties?.sessionID;
|
|
@@ -209,33 +338,38 @@ async function isChildSession(client, sessionID) {
|
|
|
209
338
|
return false;
|
|
210
339
|
}
|
|
211
340
|
}
|
|
212
|
-
|
|
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 }) => {
|
|
213
354
|
const config = loadConfig();
|
|
355
|
+
const projectName = directory ? directory.split("/").pop() ?? null : null;
|
|
214
356
|
return {
|
|
215
357
|
event: async ({ event }) => {
|
|
216
358
|
if (event.type === "permission.updated" || event.type === "permission.asked") {
|
|
217
|
-
|
|
218
|
-
await playRandomSound(config);
|
|
219
|
-
}
|
|
359
|
+
await handleEvent(config, "permission", projectName);
|
|
220
360
|
}
|
|
221
361
|
if (event.type === "session.idle") {
|
|
222
362
|
const sessionID = getSessionIDFromEvent(event);
|
|
223
363
|
if (sessionID) {
|
|
224
364
|
const isChild = await isChildSession(client, sessionID);
|
|
225
365
|
const eventType = isChild ? "subagent_complete" : "complete";
|
|
226
|
-
|
|
227
|
-
await playRandomSound(config);
|
|
228
|
-
}
|
|
366
|
+
await handleEvent(config, eventType, projectName);
|
|
229
367
|
} else {
|
|
230
|
-
|
|
231
|
-
await playRandomSound(config);
|
|
232
|
-
}
|
|
368
|
+
await handleEvent(config, "complete", projectName);
|
|
233
369
|
}
|
|
234
370
|
}
|
|
235
371
|
if (event.type === "session.error") {
|
|
236
|
-
|
|
237
|
-
await playRandomSound(config);
|
|
238
|
-
}
|
|
372
|
+
await handleEvent(config, "error", projectName);
|
|
239
373
|
}
|
|
240
374
|
}
|
|
241
375
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-probleemwijken",
|
|
3
|
-
"version": "1.
|
|
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",
|