opencode-discord-notify 0.8.0 → 0.10.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 +24 -22
- package/dist/index.js +64 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OPENCODE-DISCORD-NOTIFY
|
|
2
2
|
|
|
3
|
-
[](LICENSE)
|
|
4
|
-
[](https://www.npmjs.com/package/opencode-discord-notify)
|
|
5
|
-
[](https://www.npmjs.com/package/opencode-discord-notify)
|
|
6
3
|
[](https://www.npmjs.com/package/opencode-discord-notify)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-discord-notify)
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-discord-notify)
|
|
7
6
|

|
|
8
7
|

|
|
9
8
|

|
|
10
9
|
|
|
10
|
+
> **Requires OpenCode v1.1.1 or later** (due to breaking changes in the permission event system)
|
|
11
|
+
|
|
11
12
|
<p align="center">
|
|
12
13
|
<img src="assets/image/sample-forum-ch.png" width="700" alt="Discord Forum channel example" />
|
|
13
14
|
</p>
|
|
@@ -60,19 +61,19 @@ export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."
|
|
|
60
61
|
|
|
61
62
|
### Environment Variables
|
|
62
63
|
|
|
63
|
-
| Variable | Required | Default | Description
|
|
64
|
-
| ----------------------------------------------- | -------- | -------------------------------------------- |
|
|
65
|
-
| `DISCORD_WEBHOOK_URL` | ✅ Yes | - | Discord webhook URL. Plugin is disabled if not set.
|
|
66
|
-
| `DISCORD_WEBHOOK_USERNAME` | ❌ No | - | Custom username for webhook posts
|
|
67
|
-
| `DISCORD_WEBHOOK_AVATAR_URL` | ❌ No | - | Custom avatar URL for webhook posts
|
|
68
|
-
| `DISCORD_WEBHOOK_COMPLETE_MENTION` | ❌ No | - | Add `@everyone` or `@here` to session completion/error notifications
|
|
69
|
-
| `DISCORD_WEBHOOK_PERMISSION_MENTION` | ❌ No | - | Add `@everyone` or `@here` to permission request notifications
|
|
70
|
-
| `DISCORD_WEBHOOK_COMPLETE_INCLUDE_LAST_MESSAGE` | ❌ No | `1` | Set to `0` to exclude the last assistant message from session completion notifications
|
|
71
|
-
| `DISCORD_WEBHOOK_EXCLUDE_INPUT_CONTEXT` | ❌ No | `1` | Set to `0` to include file context in notifications
|
|
72
|
-
| `DISCORD_WEBHOOK_SHOW_ERROR_ALERT` | ❌ No | `1` | Set to `0` to disable error toast notifications
|
|
73
|
-
| `DISCORD_SEND_PARAMS` | ❌ No | - | Comma-separated embed fields: `sessionID,permissionID,
|
|
74
|
-
| `DISCORD_WEBHOOK_FALLBACK_URL` | ❌ No | - | Fallback webhook URL for text channel (sends mentions here too for guaranteed ping)
|
|
75
|
-
| `DISCORD_NOTIFY_QUEUE_DB_PATH` | ❌ No | `~/.config/opencode/discord-notify-queue.db` | Custom path for the persistent queue database
|
|
64
|
+
| Variable | Required | Default | Description |
|
|
65
|
+
| ----------------------------------------------- | -------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `DISCORD_WEBHOOK_URL` | ✅ Yes | - | Discord webhook URL. Plugin is disabled if not set. |
|
|
67
|
+
| `DISCORD_WEBHOOK_USERNAME` | ❌ No | - | Custom username for webhook posts |
|
|
68
|
+
| `DISCORD_WEBHOOK_AVATAR_URL` | ❌ No | - | Custom avatar URL for webhook posts |
|
|
69
|
+
| `DISCORD_WEBHOOK_COMPLETE_MENTION` | ❌ No | - | Add `@everyone` or `@here` to session completion/error notifications |
|
|
70
|
+
| `DISCORD_WEBHOOK_PERMISSION_MENTION` | ❌ No | - | Add `@everyone` or `@here` to permission request notifications |
|
|
71
|
+
| `DISCORD_WEBHOOK_COMPLETE_INCLUDE_LAST_MESSAGE` | ❌ No | `1` | Set to `0` to exclude the last assistant message from session completion notifications |
|
|
72
|
+
| `DISCORD_WEBHOOK_EXCLUDE_INPUT_CONTEXT` | ❌ No | `1` | Set to `0` to include file context in notifications |
|
|
73
|
+
| `DISCORD_WEBHOOK_SHOW_ERROR_ALERT` | ❌ No | `1` | Set to `0` to disable error toast notifications |
|
|
74
|
+
| `DISCORD_SEND_PARAMS` | ❌ No | - | Comma-separated embed fields: `sessionID,permissionID,permission,patterns,messageID,callID,partID,role,directory,projectID` |
|
|
75
|
+
| `DISCORD_WEBHOOK_FALLBACK_URL` | ❌ No | - | Fallback webhook URL for text channel (sends mentions here too for guaranteed ping) |
|
|
76
|
+
| `DISCORD_NOTIFY_QUEUE_DB_PATH` | ❌ No | `~/.config/opencode/discord-notify-queue.db` | Custom path for the persistent queue database |
|
|
76
77
|
|
|
77
78
|
### Example Configuration
|
|
78
79
|
|
|
@@ -107,7 +108,7 @@ export DISCORD_WEBHOOK_AVATAR_URL="https://example.com/avatar.png"
|
|
|
107
108
|
### Supported Events
|
|
108
109
|
|
|
109
110
|
- **`session.created`**: Queues session start notification (sent when thread info is available)
|
|
110
|
-
- **`permission.
|
|
111
|
+
- **`permission.asked`**: Posts permission request immediately
|
|
111
112
|
- **`session.idle`**: Posts session completion notification
|
|
112
113
|
- Includes the last assistant message in the embed description by default (customizable via `DISCORD_WEBHOOK_COMPLETE_INCLUDE_LAST_MESSAGE`)
|
|
113
114
|
- Message is truncated to 4096 characters if needed
|
|
@@ -216,7 +217,7 @@ Controls which metadata fields appear in embeds.
|
|
|
216
217
|
|
|
217
218
|
**Allowed keys:**
|
|
218
219
|
|
|
219
|
-
- `sessionID`, `permissionID`, `
|
|
220
|
+
- `sessionID`, `permissionID`, `permission`, `patterns`, `messageID`, `callID`, `partID`, `role`, `directory`, `projectID`
|
|
220
221
|
|
|
221
222
|
**Default behavior (unset/empty):**
|
|
222
223
|
|
|
@@ -225,7 +226,7 @@ Controls which metadata fields appear in embeds.
|
|
|
225
226
|
**To send all fields:**
|
|
226
227
|
|
|
227
228
|
```bash
|
|
228
|
-
export DISCORD_SEND_PARAMS="sessionID,permissionID,
|
|
229
|
+
export DISCORD_SEND_PARAMS="sessionID,permissionID,permission,patterns,messageID,callID,partID,role,directory,projectID"
|
|
229
230
|
```
|
|
230
231
|
|
|
231
232
|
**Note:** `session.created` always includes `sessionID` regardless of this setting.
|
|
@@ -253,8 +254,9 @@ Main implementation: `src/index.ts`
|
|
|
253
254
|
## Roadmap
|
|
254
255
|
|
|
255
256
|
- [ ] Support multiple webhooks for routing by event type
|
|
256
|
-
- [ ]
|
|
257
|
-
- [
|
|
257
|
+
- [ ] Message filtering/customization
|
|
258
|
+
- [x] Customizable notification templates
|
|
259
|
+
- [x] Configuration file support (e.g., `opencode-discord-notify.config.json`)
|
|
258
260
|
- [x] Enhanced rate limit handling (smarter retry logic, message queuing)
|
|
259
261
|
- [x] CI/CD (automated linting, formatting, testing)
|
|
260
262
|
|
package/dist/index.js
CHANGED
|
@@ -1,42 +1,3 @@
|
|
|
1
|
-
// src/utils/db.ts
|
|
2
|
-
import { Database } from "bun:sqlite";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import path from "path";
|
|
6
|
-
function getDbPath() {
|
|
7
|
-
if (process.env.NODE_ENV === "test" || process.env.VITEST === "true") {
|
|
8
|
-
return ":memory:";
|
|
9
|
-
}
|
|
10
|
-
return process.env.DISCORD_NOTIFY_QUEUE_DB_PATH || path.join(os.homedir(), ".config", "opencode", "discord-notify-queue.db");
|
|
11
|
-
}
|
|
12
|
-
function initDatabase() {
|
|
13
|
-
const dbPath = getDbPath();
|
|
14
|
-
if (dbPath !== ":memory:") {
|
|
15
|
-
const dbDir = path.dirname(dbPath);
|
|
16
|
-
if (!fs.existsSync(dbDir)) {
|
|
17
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const db = new Database(dbPath);
|
|
21
|
-
db.run("PRAGMA journal_mode = WAL;");
|
|
22
|
-
db.run(`
|
|
23
|
-
CREATE TABLE IF NOT EXISTS discord_queue (
|
|
24
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
-
session_id TEXT NOT NULL,
|
|
26
|
-
thread_id TEXT,
|
|
27
|
-
webhook_body TEXT NOT NULL,
|
|
28
|
-
created_at INTEGER NOT NULL,
|
|
29
|
-
retry_count INTEGER DEFAULT 0,
|
|
30
|
-
last_error TEXT
|
|
31
|
-
);
|
|
32
|
-
`);
|
|
33
|
-
db.run(`
|
|
34
|
-
CREATE INDEX IF NOT EXISTS idx_session_created
|
|
35
|
-
ON discord_queue(session_id, created_at);
|
|
36
|
-
`);
|
|
37
|
-
return db;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
1
|
// src/queue/persistent-queue.ts
|
|
41
2
|
var PersistentQueue = class {
|
|
42
3
|
db;
|
|
@@ -205,6 +166,45 @@ var QueueWorker = class {
|
|
|
205
166
|
}
|
|
206
167
|
};
|
|
207
168
|
|
|
169
|
+
// src/utils/db.ts
|
|
170
|
+
import { Database } from "bun:sqlite";
|
|
171
|
+
import fs from "fs";
|
|
172
|
+
import os from "os";
|
|
173
|
+
import path from "path";
|
|
174
|
+
function getDbPath() {
|
|
175
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST === "true") {
|
|
176
|
+
return ":memory:";
|
|
177
|
+
}
|
|
178
|
+
return process.env.DISCORD_NOTIFY_QUEUE_DB_PATH || path.join(os.homedir(), ".config", "opencode", "discord-notify-queue.db");
|
|
179
|
+
}
|
|
180
|
+
function initDatabase() {
|
|
181
|
+
const dbPath = getDbPath();
|
|
182
|
+
if (dbPath !== ":memory:") {
|
|
183
|
+
const dbDir = path.dirname(dbPath);
|
|
184
|
+
if (!fs.existsSync(dbDir)) {
|
|
185
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const db = new Database(dbPath);
|
|
189
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
190
|
+
db.run(`
|
|
191
|
+
CREATE TABLE IF NOT EXISTS discord_queue (
|
|
192
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
193
|
+
session_id TEXT NOT NULL,
|
|
194
|
+
thread_id TEXT,
|
|
195
|
+
webhook_body TEXT NOT NULL,
|
|
196
|
+
created_at INTEGER NOT NULL,
|
|
197
|
+
retry_count INTEGER DEFAULT 0,
|
|
198
|
+
last_error TEXT
|
|
199
|
+
);
|
|
200
|
+
`);
|
|
201
|
+
db.run(`
|
|
202
|
+
CREATE INDEX IF NOT EXISTS idx_session_created
|
|
203
|
+
ON discord_queue(session_id, created_at);
|
|
204
|
+
`);
|
|
205
|
+
return db;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
208
|
// src/index.ts
|
|
209
209
|
var DISCORD_FIELD_VALUE_MAX_LENGTH = 1024;
|
|
210
210
|
var DISCORD_EMBED_DESCRIPTION_MAX_LENGTH = 4096;
|
|
@@ -220,8 +220,8 @@ var DEFAULT_RATE_LIMIT_WAIT_MS = 1e4;
|
|
|
220
220
|
var SEND_PARAM_KEYS = [
|
|
221
221
|
"sessionID",
|
|
222
222
|
"permissionID",
|
|
223
|
-
"
|
|
224
|
-
"
|
|
223
|
+
"permission",
|
|
224
|
+
"patterns",
|
|
225
225
|
"messageID",
|
|
226
226
|
"callID",
|
|
227
227
|
"partID",
|
|
@@ -255,10 +255,7 @@ function buildFields(fields, inline = false) {
|
|
|
255
255
|
for (const [name, rawValue] of fields) {
|
|
256
256
|
const value = safeString(rawValue);
|
|
257
257
|
if (!value) continue;
|
|
258
|
-
const truncatedValue = value.length > DISCORD_FIELD_VALUE_MAX_LENGTH ? value.slice(
|
|
259
|
-
0,
|
|
260
|
-
DISCORD_FIELD_VALUE_MAX_LENGTH - ELLIPSIS_LENGTH
|
|
261
|
-
) + ELLIPSIS : value;
|
|
258
|
+
const truncatedValue = value.length > DISCORD_FIELD_VALUE_MAX_LENGTH ? value.slice(0, DISCORD_FIELD_VALUE_MAX_LENGTH - ELLIPSIS_LENGTH) + ELLIPSIS : value;
|
|
262
259
|
result.push({
|
|
263
260
|
name,
|
|
264
261
|
value: truncatedValue,
|
|
@@ -492,10 +489,7 @@ async function postFallbackIfNeeded(input, deps) {
|
|
|
492
489
|
fallbackBody.embeds = [
|
|
493
490
|
{
|
|
494
491
|
...originalEmbed,
|
|
495
|
-
fields: [
|
|
496
|
-
...originalEmbed.fields ?? [],
|
|
497
|
-
...additionalFields ?? []
|
|
498
|
-
]
|
|
492
|
+
fields: [...originalEmbed.fields ?? [], ...additionalFields ?? []]
|
|
499
493
|
}
|
|
500
494
|
];
|
|
501
495
|
}
|
|
@@ -733,11 +727,13 @@ var plugin = async ({ client }) => {
|
|
|
733
727
|
});
|
|
734
728
|
return;
|
|
735
729
|
}
|
|
736
|
-
case "permission.
|
|
730
|
+
case "permission.asked": {
|
|
737
731
|
const p = event.properties;
|
|
738
732
|
const sessionID = p?.sessionID;
|
|
739
733
|
if (!sessionID) return;
|
|
740
734
|
const mention = buildPermissionMention();
|
|
735
|
+
const patternsArray = p?.patterns;
|
|
736
|
+
const patternsStr = Array.isArray(patternsArray) ? patternsArray.join(", ") : void 0;
|
|
741
737
|
const embed = {
|
|
742
738
|
title: "Permission required",
|
|
743
739
|
description: p?.title,
|
|
@@ -748,17 +744,23 @@ var plugin = async ({ client }) => {
|
|
|
748
744
|
[
|
|
749
745
|
["sessionID", sessionID],
|
|
750
746
|
["permissionID", p?.id],
|
|
751
|
-
["
|
|
752
|
-
["
|
|
753
|
-
["messageID", p?.messageID],
|
|
754
|
-
["callID", p?.callID]
|
|
747
|
+
["permission", p?.permission],
|
|
748
|
+
["patterns", patternsStr],
|
|
749
|
+
["messageID", p?.tool?.messageID],
|
|
750
|
+
["callID", p?.tool?.callID]
|
|
755
751
|
],
|
|
756
752
|
sendParams
|
|
757
753
|
)
|
|
758
754
|
)
|
|
759
755
|
};
|
|
756
|
+
const permissionType = p?.permission || "";
|
|
757
|
+
const permissionDetail = patternsStr || "";
|
|
758
|
+
const permissionSummary = truncateText(
|
|
759
|
+
p?.title || (permissionType && permissionDetail ? `${permissionType}(${permissionDetail})` : permissionType || "Permission requested"),
|
|
760
|
+
100
|
|
761
|
+
);
|
|
760
762
|
const body = {
|
|
761
|
-
content: mention ? `${mention.content}` : void 0,
|
|
763
|
+
content: mention ? `${mention.content} Permission: ${permissionSummary}` : void 0,
|
|
762
764
|
allowed_mentions: mention?.allowed_mentions,
|
|
763
765
|
embeds: [embed]
|
|
764
766
|
};
|
|
@@ -785,16 +787,16 @@ var plugin = async ({ client }) => {
|
|
|
785
787
|
const embed = {
|
|
786
788
|
title: "Session completed",
|
|
787
789
|
color: COLORS.success,
|
|
788
|
-
description: lastMessage ? truncateText(
|
|
790
|
+
description: lastMessage ? truncateText(
|
|
791
|
+
lastMessage,
|
|
792
|
+
DISCORD_EMBED_DESCRIPTION_MAX_LENGTH
|
|
793
|
+
) : void 0,
|
|
789
794
|
fields: buildFields(
|
|
790
|
-
filterSendFields(
|
|
791
|
-
[["sessionID", sessionID]],
|
|
792
|
-
sendParams
|
|
793
|
-
)
|
|
795
|
+
filterSendFields([["sessionID", sessionID]], sendParams)
|
|
794
796
|
)
|
|
795
797
|
};
|
|
796
798
|
const body = {
|
|
797
|
-
content: mention ? `${mention.content}` : void 0,
|
|
799
|
+
content: mention ? `${mention.content} Session completed` : void 0,
|
|
798
800
|
allowed_mentions: mention?.allowed_mentions,
|
|
799
801
|
embeds: [embed]
|
|
800
802
|
};
|
|
@@ -838,8 +840,7 @@ var plugin = async ({ client }) => {
|
|
|
838
840
|
if (!sessionID) return;
|
|
839
841
|
const mention = buildCompleteMention();
|
|
840
842
|
const body = {
|
|
841
|
-
|
|
842
|
-
content: mention ? `${mention.content}` : void 0,
|
|
843
|
+
content: mention ? `${mention.content} Session error` : void 0,
|
|
843
844
|
allowed_mentions: mention?.allowed_mentions,
|
|
844
845
|
embeds: [embed]
|
|
845
846
|
};
|