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.
Files changed (3) hide show
  1. package/README.md +24 -22
  2. package/dist/index.js +64 -63
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
- # opencode-discord-notify
1
+ # OPENCODE-DISCORD-NOTIFY
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
- [![npm version](https://img.shields.io/npm/v/opencode-discord-notify?logo=npm&logoColor=fff)](https://www.npmjs.com/package/opencode-discord-notify)
5
- [![npm downloads](https://img.shields.io/npm/dm/opencode-discord-notify?logo=npm&logoColor=fff)](https://www.npmjs.com/package/opencode-discord-notify)
6
3
  [![npm license](https://img.shields.io/npm/l/opencode-discord-notify?logo=npm&logoColor=fff)](https://www.npmjs.com/package/opencode-discord-notify)
4
+ [![npm downloads](https://img.shields.io/npm/dt/opencode-discord-notify?logo=npm&logoColor=fff)](https://www.npmjs.com/package/opencode-discord-notify)
5
+ [![npm version](https://img.shields.io/npm/v/opencode-discord-notify?logo=npm&logoColor=fff)](https://www.npmjs.com/package/opencode-discord-notify)
7
6
  ![OpenCode Plugin](https://img.shields.io/badge/OpenCode-Plugin-4c8bf5)
8
7
  ![Discord Webhook](https://img.shields.io/badge/Discord-Webhook-5865F2?logo=discord&logoColor=fff)
9
8
  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff)
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,type,pattern,messageID,callID,partID,role,directory,projectID` |
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.updated`**: Posts permission request immediately
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`, `type`, `pattern`, `messageID`, `callID`, `partID`, `role`, `directory`, `projectID`
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,type,pattern,messageID,callID,partID,role,directory,projectID"
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
- - [ ] Customizable notification templates
257
- - [ ] Configuration file support (e.g., `opencode-discord-notify.config.json`)
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
- "type",
224
- "pattern",
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.updated": {
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
- ["type", p?.type],
752
- ["pattern", p?.pattern],
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(lastMessage, DISCORD_EMBED_DESCRIPTION_MAX_LENGTH) : void 0,
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
- // 🐛 既存バグ修正: `$Session error` `${mention.content}`
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-discord-notify",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A plugin that posts OpenCode events to a Discord webhook.",
5
5
  "license": "MIT",
6
6
  "type": "module",