@sillybit/opencode-plugin-everynotify 0.1.1 → 0.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 +124 -4
- package/dist/dispatcher.d.ts +3 -1
- package/dist/index.js +146 -8
- package/dist/logger.d.ts +25 -0
- package/dist/types.d.ts +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EveryNotify — Multi-Service Notifications for opencode
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/opencode-plugin-everynotify)
|
|
3
|
+
[](https://www.npmjs.com/package/@sillybit/opencode-plugin-everynotify)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
6
|
EveryNotify is a lightweight, zero-dependency notification plugin for [opencode](https://github.com/opencode-ai/plugin) designed to keep you informed about your AI-driven development sessions. It dispatches notifications to multiple services simultaneously, ensuring you stay updated on task progress, errors, and interaction requests, even when you're not actively monitoring your terminal.
|
|
@@ -25,15 +25,54 @@ In long-running development tasks or deep research sessions, it's common to swit
|
|
|
25
25
|
- ✅ **Fault Tolerance**: Isolated service calls ensure that a failure in one provider doesn't block others.
|
|
26
26
|
- ✅ **Zero Runtime Dependencies**: Built entirely on standard Node.js APIs and native `fetch()`.
|
|
27
27
|
- ✅ **Privacy & Control**: Completely opt-in; no notifications are sent until you enable and configure a service.
|
|
28
|
+
- ✅ **Event Filtering**: Selectively enable or disable notifications for specific event types.
|
|
29
|
+
- ✅ **Rich Content**: Notifications include the actual assistant response text for better context.
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
30
32
|
|
|
31
33
|
Install EveryNotify into your opencode environment using npm:
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
npm install opencode-plugin-everynotify
|
|
36
|
+
npm install @sillybit/opencode-plugin-everynotify
|
|
35
37
|
```
|
|
36
38
|
|
|
39
|
+
## Using with opencode
|
|
40
|
+
|
|
41
|
+
opencode does not auto-load npm packages; you must **register the plugin** in your opencode config so it is loaded at startup.
|
|
42
|
+
|
|
43
|
+
### Option 1: Register via config (recommended)
|
|
44
|
+
|
|
45
|
+
Add EveryNotify to the `plugin` array in your opencode config. opencode will install and load it automatically (using Bun) when you run opencode.
|
|
46
|
+
|
|
47
|
+
**Global config** (all projects): edit `~/.config/opencode/opencode.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"plugin": ["@sillybit/opencode-plugin-everynotify"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Project config** (single project): add or edit `opencode.json` in your project root:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"plugin": ["@sillybit/opencode-plugin-everynotify"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can list multiple plugins in the same array, e.g. `["@sillybit/opencode-plugin-everynotify", "opencode-wakatime"]`.
|
|
64
|
+
|
|
65
|
+
### Option 2: Local plugin directory
|
|
66
|
+
|
|
67
|
+
Alternatively, place the plugin in opencode’s plugin directory so it is loaded as a local plugin:
|
|
68
|
+
|
|
69
|
+
- **Project-only**: `.opencode/plugins/` (e.g. symlink or copy from `node_modules/@sillybit/opencode-plugin-everynotify`)
|
|
70
|
+
- **All projects**: `~/.config/opencode/plugins/`
|
|
71
|
+
|
|
72
|
+
Files in these directories are loaded automatically; no `plugin` entry in opencode config is required for them.
|
|
73
|
+
|
|
74
|
+
After the plugin is loaded, configure your notification services (see [Configuration](#configuration)) and optionally add a `.everynotify.json` for per-project overrides.
|
|
75
|
+
|
|
37
76
|
## Configuration
|
|
38
77
|
|
|
39
78
|
EveryNotify utilizes a simple JSON configuration file named `.everynotify.json`. The plugin aggregates configuration from two potential scopes:
|
|
@@ -67,15 +106,96 @@ Create your `.everynotify.json` with the tokens for the services you want to use
|
|
|
67
106
|
"discord": {
|
|
68
107
|
"enabled": false,
|
|
69
108
|
"webhookUrl": "https://discord.com/api/webhooks/0000/XXXX"
|
|
109
|
+
},
|
|
110
|
+
"log": {
|
|
111
|
+
"enabled": true,
|
|
112
|
+
"level": "warn"
|
|
113
|
+
},
|
|
114
|
+
"events": {
|
|
115
|
+
"complete": true,
|
|
116
|
+
"subagent_complete": true,
|
|
117
|
+
"error": true,
|
|
118
|
+
"permission": false,
|
|
119
|
+
"question": true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration Options
|
|
125
|
+
|
|
126
|
+
### Event Filtering
|
|
127
|
+
|
|
128
|
+
You can control which events trigger notifications by adding an `events` block to your `.everynotify.json`. All events are enabled (`true`) by default to ensure backward compatibility.
|
|
129
|
+
|
|
130
|
+
| Event | Description |
|
|
131
|
+
| ------------------- | -------------------------------------------------------------------------- |
|
|
132
|
+
| `complete` | The main opencode session has finished its task and is now idle. |
|
|
133
|
+
| `subagent_complete` | A subagent has completed its specific assigned task. |
|
|
134
|
+
| `error` | A fatal error or crash occurred during the session. |
|
|
135
|
+
| `permission` | opencode is waiting for you to grant permission for a tool or file access. |
|
|
136
|
+
| `question` | The `question` tool was used to ask you for clarification. |
|
|
137
|
+
|
|
138
|
+
### Rich Message Content
|
|
139
|
+
|
|
140
|
+
EveryNotify provides rich context in its notifications by extracting the assistant's last response. Instead of generic "Task completed" messages, you receive the actual summary or answer provided by opencode.
|
|
141
|
+
|
|
142
|
+
**Notification Priority:**
|
|
143
|
+
|
|
144
|
+
1. **Errors**: The specific error message always takes top priority.
|
|
145
|
+
2. **Assistant Text**: The actual text from the assistant's final response.
|
|
146
|
+
3. **Fallback**: If no text is found, it defaults to a generic "Task completed" message.
|
|
147
|
+
|
|
148
|
+
**Message Format Example:**
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
[complete] my-project
|
|
152
|
+
I've successfully implemented the authentication system with JWT tokens and refresh token rotation. The API endpoints are secured and tests are passing. (elapsed: 2m 18s)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### File-Based Logging
|
|
156
|
+
|
|
157
|
+
EveryNotify includes an optional file-based logging system to help you troubleshoot service delivery or configuration issues without cluttering your terminal.
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"log": {
|
|
162
|
+
"enabled": true,
|
|
163
|
+
"level": "warn"
|
|
70
164
|
}
|
|
71
165
|
}
|
|
72
166
|
```
|
|
73
167
|
|
|
168
|
+
**Options:**
|
|
169
|
+
|
|
170
|
+
- `enabled`: Set to `true` to activate file-based logging (default: `false`).
|
|
171
|
+
- `level`: The minimum severity to log.
|
|
172
|
+
- `"error"`: Only records failed service dispatches and critical system errors.
|
|
173
|
+
- `"warn"`: Records errors plus non-critical warnings like missing configuration files (default).
|
|
174
|
+
|
|
175
|
+
**Log File Details:**
|
|
176
|
+
|
|
177
|
+
- **Location**: `~/.config/opencode/.everynotify.log`
|
|
178
|
+
- **Format**: `[ISO-8601] [LEVEL] [EveryNotify] Message`
|
|
179
|
+
- **Rotation**: Logs are automatically rotated every 7 days (based on file modification time).
|
|
180
|
+
- **Cleanup**: EveryNotify maintains a maximum of 4 rotated files (e.g., `.everynotify.log.2026-02-01`), automatically deleting the oldest.
|
|
181
|
+
|
|
182
|
+
**Example Entry:**
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
[2026-02-08T14:30:45.123Z] [ERROR] [EveryNotify] Service dispatch failed: Pushover: Network timeout
|
|
186
|
+
[2026-02-08T14:30:45.456Z] [WARN] [EveryNotify] Config file not found at ~/.config/opencode/.everynotify.json
|
|
187
|
+
```
|
|
188
|
+
|
|
74
189
|
## Usage
|
|
75
190
|
|
|
76
|
-
EveryNotify is a "fire-and-forget" plugin. Once
|
|
191
|
+
EveryNotify is a "fire-and-forget" plugin. Once you have [registered it with opencode](#using-with-opencode) and [configured](#configuration) at least one service with `"enabled": true`, it runs automatically. opencode loads the plugin at startup; EveryNotify then listens for events and sends notifications in the background with no further action needed.
|
|
192
|
+
|
|
193
|
+
When an event is triggered, EveryNotify builds a rich message containing the event type, project name, the actual assistant response, and the total elapsed time.
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
`[complete] my-project: I've finished the refactor. (elapsed: 12m 30s)`
|
|
77
197
|
|
|
78
|
-
|
|
198
|
+
Notifications are dispatched to all services marked as `"enabled": true`.
|
|
79
199
|
|
|
80
200
|
## Supported Services
|
|
81
201
|
|
package/dist/dispatcher.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Error isolation: One service failing doesn't block others (Promise.allSettled)
|
|
8
8
|
*/
|
|
9
9
|
import type { EverynotifyConfig, NotificationPayload } from "./types";
|
|
10
|
+
import type { Logger } from "./logger";
|
|
10
11
|
/**
|
|
11
12
|
* Truncate text to max length, appending "… [truncated]" if over limit
|
|
12
13
|
* Shared utility function used by all services
|
|
@@ -22,7 +23,8 @@ interface Dispatcher {
|
|
|
22
23
|
* Create a dispatcher that sends notifications to all enabled services
|
|
23
24
|
*
|
|
24
25
|
* @param config - EverynotifyConfig with enabled services
|
|
26
|
+
* @param logger - Logger instance for error logging
|
|
25
27
|
* @returns Dispatcher with dispatch() function
|
|
26
28
|
*/
|
|
27
|
-
export declare function createDispatcher(config: EverynotifyConfig): Dispatcher;
|
|
29
|
+
export declare function createDispatcher(config: EverynotifyConfig, logger: Logger): Dispatcher;
|
|
28
30
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as path3 from "path";
|
|
3
3
|
|
|
4
4
|
// src/config.ts
|
|
5
5
|
import * as fs from "fs";
|
|
@@ -24,6 +24,17 @@ var DEFAULT_CONFIG = {
|
|
|
24
24
|
discord: {
|
|
25
25
|
enabled: false,
|
|
26
26
|
webhookUrl: ""
|
|
27
|
+
},
|
|
28
|
+
log: {
|
|
29
|
+
enabled: false,
|
|
30
|
+
level: "warn"
|
|
31
|
+
},
|
|
32
|
+
events: {
|
|
33
|
+
complete: true,
|
|
34
|
+
subagent_complete: true,
|
|
35
|
+
error: true,
|
|
36
|
+
permission: true,
|
|
37
|
+
question: true
|
|
27
38
|
}
|
|
28
39
|
};
|
|
29
40
|
function getConfigPath(scope, directory) {
|
|
@@ -46,6 +57,12 @@ function deepMerge(target, source) {
|
|
|
46
57
|
if (source.discord) {
|
|
47
58
|
result.discord = { ...result.discord, ...source.discord };
|
|
48
59
|
}
|
|
60
|
+
if (source.log) {
|
|
61
|
+
result.log = { ...result.log, ...source.log };
|
|
62
|
+
}
|
|
63
|
+
if (source.events) {
|
|
64
|
+
result.events = { ...result.events, ...source.events };
|
|
65
|
+
}
|
|
49
66
|
return result;
|
|
50
67
|
}
|
|
51
68
|
function loadConfigFile(filePath) {
|
|
@@ -232,7 +249,7 @@ async function send4(config, payload, signal) {
|
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
// src/dispatcher.ts
|
|
235
|
-
function createDispatcher(config) {
|
|
252
|
+
function createDispatcher(config, logger) {
|
|
236
253
|
const services = [];
|
|
237
254
|
if (config.pushover.enabled) {
|
|
238
255
|
services.push({
|
|
@@ -285,22 +302,109 @@ function createDispatcher(config) {
|
|
|
285
302
|
const results = await Promise.allSettled(promises);
|
|
286
303
|
results.forEach((result, index) => {
|
|
287
304
|
if (result.status === "rejected") {
|
|
305
|
+
const errorMsg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
288
306
|
console.error(`[EveryNotify] ${services[index].name} failed:`, result.reason);
|
|
307
|
+
logger.error(`${services[index].name} failed: ${errorMsg}`);
|
|
289
308
|
}
|
|
290
309
|
});
|
|
291
310
|
}
|
|
292
311
|
return { dispatch };
|
|
293
312
|
}
|
|
294
313
|
|
|
314
|
+
// src/logger.ts
|
|
315
|
+
import * as fs2 from "node:fs";
|
|
316
|
+
import * as path2 from "node:path";
|
|
317
|
+
import * as os2 from "node:os";
|
|
318
|
+
function getLogFilePath() {
|
|
319
|
+
return path2.join(os2.homedir(), ".config", "opencode", ".everynotify.log");
|
|
320
|
+
}
|
|
321
|
+
function createLogger(config) {
|
|
322
|
+
if (!config.log.enabled) {
|
|
323
|
+
return { error: () => {}, warn: () => {} };
|
|
324
|
+
}
|
|
325
|
+
const logFilePath = getLogFilePath();
|
|
326
|
+
const logDir = path2.dirname(logFilePath);
|
|
327
|
+
const level = config.log.level || "warn";
|
|
328
|
+
let disabled = false;
|
|
329
|
+
try {
|
|
330
|
+
fs2.mkdirSync(logDir, { recursive: true });
|
|
331
|
+
} catch (error) {
|
|
332
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
333
|
+
console.error(`[EveryNotify] Failed to create log directory: ${message}`);
|
|
334
|
+
disabled = true;
|
|
335
|
+
}
|
|
336
|
+
function writeLog(levelStr, msg) {
|
|
337
|
+
if (disabled)
|
|
338
|
+
return;
|
|
339
|
+
try {
|
|
340
|
+
rotateIfNeeded(logFilePath);
|
|
341
|
+
const timestamp = new Date().toISOString();
|
|
342
|
+
const line = `[${timestamp}] [${levelStr}] [EveryNotify] ${msg}
|
|
343
|
+
`;
|
|
344
|
+
fs2.appendFileSync(logFilePath, line, "utf-8");
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
347
|
+
console.error(`[EveryNotify] Log write failed: ${message}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
error(msg) {
|
|
352
|
+
writeLog("ERROR", msg);
|
|
353
|
+
},
|
|
354
|
+
warn(msg) {
|
|
355
|
+
if (level === "error")
|
|
356
|
+
return;
|
|
357
|
+
writeLog("WARN", msg);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function rotateIfNeeded(logFilePath) {
|
|
362
|
+
try {
|
|
363
|
+
const stat = fs2.statSync(logFilePath);
|
|
364
|
+
const ageMs = Date.now() - stat.mtime.getTime();
|
|
365
|
+
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
|
|
366
|
+
if (ageMs > sevenDaysMs) {
|
|
367
|
+
const mtimeDate = stat.mtime.toISOString().split("T")[0];
|
|
368
|
+
const rotatedPath = `${logFilePath}.${mtimeDate}`;
|
|
369
|
+
fs2.renameSync(logFilePath, rotatedPath);
|
|
370
|
+
cleanupRotatedFiles(logFilePath);
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error.code !== "ENOENT") {
|
|
374
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
375
|
+
console.error(`[EveryNotify] Rotation check failed: ${message}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function cleanupRotatedFiles(logFilePath) {
|
|
380
|
+
try {
|
|
381
|
+
const dir = path2.dirname(logFilePath);
|
|
382
|
+
const baseName = path2.basename(logFilePath);
|
|
383
|
+
const files = fs2.readdirSync(dir).filter((f) => f.startsWith(`${baseName}.`) && /\d{4}-\d{2}-\d{2}$/.test(f)).sort();
|
|
384
|
+
while (files.length > 4) {
|
|
385
|
+
const oldest = files.shift();
|
|
386
|
+
fs2.unlinkSync(path2.join(dir, oldest));
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
console.error(`[EveryNotify] Cleanup failed: ${message}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
295
394
|
// src/index.ts
|
|
296
395
|
var EverynotifyPlugin = async (input) => {
|
|
297
396
|
const { client, directory } = input;
|
|
298
397
|
const config = loadConfig(directory);
|
|
299
|
-
const
|
|
398
|
+
const logger = createLogger(config);
|
|
399
|
+
const { dispatch } = createDispatcher(config, logger);
|
|
400
|
+
function isEventEnabled(eventType) {
|
|
401
|
+
return config.events[eventType] !== false;
|
|
402
|
+
}
|
|
300
403
|
async function buildPayload(eventType, sessionID, extraMessage) {
|
|
301
|
-
const projectName = directory ?
|
|
404
|
+
const projectName = directory ? path3.basename(directory) : null;
|
|
302
405
|
let elapsedSeconds = null;
|
|
303
406
|
let isSubagent = false;
|
|
407
|
+
let assistantText = null;
|
|
304
408
|
try {
|
|
305
409
|
if (sessionID) {
|
|
306
410
|
const sessionResult = await client.session.get({
|
|
@@ -320,6 +424,17 @@ var EverynotifyPlugin = async (input) => {
|
|
|
320
424
|
const now = Date.now();
|
|
321
425
|
elapsedSeconds = Math.floor((now - startTime) / 1000);
|
|
322
426
|
}
|
|
427
|
+
const lastAssistantMessage = [...messages].reverse().find((msg) => msg.info?.role === "assistant");
|
|
428
|
+
if (lastAssistantMessage?.parts) {
|
|
429
|
+
const textParts = lastAssistantMessage.parts.filter((part) => part.type === "text");
|
|
430
|
+
if (textParts.length > 0) {
|
|
431
|
+
const lastTextPart = textParts[textParts.length - 1];
|
|
432
|
+
const text = lastTextPart.text?.trim();
|
|
433
|
+
if (text) {
|
|
434
|
+
assistantText = text;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
323
438
|
}
|
|
324
439
|
}
|
|
325
440
|
} catch (error) {}
|
|
@@ -328,7 +443,7 @@ var EverynotifyPlugin = async (input) => {
|
|
|
328
443
|
finalEventType = "subagent_complete";
|
|
329
444
|
}
|
|
330
445
|
const title = `[${finalEventType}] ${projectName || "opencode"}`;
|
|
331
|
-
let message = extraMessage
|
|
446
|
+
let message = extraMessage ?? assistantText ?? "Task completed";
|
|
332
447
|
if (elapsedSeconds !== null) {
|
|
333
448
|
const minutes = Math.floor(elapsedSeconds / 60);
|
|
334
449
|
const seconds = elapsedSeconds % 60;
|
|
@@ -348,40 +463,63 @@ var EverynotifyPlugin = async (input) => {
|
|
|
348
463
|
try {
|
|
349
464
|
const sessionID = event.properties?.sessionID || null;
|
|
350
465
|
if (event.type === "session.idle") {
|
|
466
|
+
if (!isEventEnabled("complete")) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
351
469
|
const payload = await buildPayload("complete", sessionID);
|
|
470
|
+
if (payload.eventType === "subagent_complete" && !isEventEnabled("subagent_complete")) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
352
473
|
await dispatch(payload);
|
|
353
474
|
} else if (event.type === "session.error") {
|
|
475
|
+
if (!isEventEnabled("error")) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
354
478
|
const rawError = event.properties?.error;
|
|
355
479
|
const errorMessage = rawError?.data?.message ?? rawError?.name ?? "Unknown error";
|
|
356
480
|
const payload = await buildPayload("error", sessionID, errorMessage);
|
|
357
481
|
await dispatch(payload);
|
|
358
482
|
} else if (event.type === "permission.updated") {
|
|
483
|
+
if (!isEventEnabled("permission")) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
359
486
|
const payload = await buildPayload("permission", sessionID);
|
|
360
487
|
await dispatch(payload);
|
|
361
488
|
}
|
|
362
489
|
} catch (error) {
|
|
363
490
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
364
491
|
console.error(`[EveryNotify] Event hook error: ${errorMsg}`);
|
|
492
|
+
logger.error(`Event hook error: ${errorMsg}`);
|
|
365
493
|
}
|
|
366
494
|
}
|
|
367
|
-
async function permissionAskHook(
|
|
495
|
+
async function permissionAskHook(input2, _output) {
|
|
368
496
|
try {
|
|
369
|
-
|
|
497
|
+
if (!isEventEnabled("permission")) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const sessionID = input2?.sessionID ?? null;
|
|
501
|
+
const payload = await buildPayload("permission", sessionID);
|
|
370
502
|
await dispatch(payload);
|
|
371
503
|
} catch (error) {
|
|
372
504
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
373
505
|
console.error(`[EveryNotify] Permission.ask hook error: ${errorMsg}`);
|
|
506
|
+
logger.error(`Permission.ask hook error: ${errorMsg}`);
|
|
374
507
|
}
|
|
375
508
|
}
|
|
376
509
|
async function toolExecuteBeforeHook(input2, _output) {
|
|
377
510
|
try {
|
|
511
|
+
if (!isEventEnabled("question")) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
378
514
|
if (input2.tool === "question") {
|
|
379
|
-
const
|
|
515
|
+
const sessionID = input2?.sessionID ?? null;
|
|
516
|
+
const payload = await buildPayload("question", sessionID);
|
|
380
517
|
await dispatch(payload);
|
|
381
518
|
}
|
|
382
519
|
} catch (error) {
|
|
383
520
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
384
521
|
console.error(`[EveryNotify] Tool.execute.before hook error: ${errorMsg}`);
|
|
522
|
+
logger.error(`Tool.execute.before hook error: ${errorMsg}`);
|
|
385
523
|
}
|
|
386
524
|
}
|
|
387
525
|
return {
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EveryNotify Plugin — File-Based Logger
|
|
3
|
+
*
|
|
4
|
+
* Provides a simple file-based logger with 7-day rotation.
|
|
5
|
+
* Writes to ~/.config/opencode/.everynotify.log with automatic cleanup.
|
|
6
|
+
*/
|
|
7
|
+
import type { EverynotifyConfig } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Logger interface with error and warn methods
|
|
10
|
+
*/
|
|
11
|
+
export interface Logger {
|
|
12
|
+
error(msg: string): void;
|
|
13
|
+
warn(msg: string): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the absolute path to the log file
|
|
17
|
+
* @returns Log file path: ~/.config/opencode/.everynotify.log
|
|
18
|
+
*/
|
|
19
|
+
export declare function getLogFilePath(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a logger instance based on configuration
|
|
22
|
+
* @param config - EveryNotify configuration object
|
|
23
|
+
* @returns Logger instance (no-op if logging disabled)
|
|
24
|
+
*/
|
|
25
|
+
export declare function createLogger(config: EverynotifyConfig): Logger;
|
package/dist/types.d.ts
CHANGED
|
@@ -62,6 +62,32 @@ export interface DiscordConfig {
|
|
|
62
62
|
enabled: boolean;
|
|
63
63
|
webhookUrl: string;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Logging configuration
|
|
67
|
+
* Controls debug output and error logging behavior
|
|
68
|
+
* - enabled: whether logging is active
|
|
69
|
+
* - level: minimum log level to output ("error" or "warn")
|
|
70
|
+
*/
|
|
71
|
+
export interface LogConfig {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
level?: "error" | "warn";
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Events configuration
|
|
77
|
+
* Controls which event types trigger notifications
|
|
78
|
+
* - complete: main session completion events
|
|
79
|
+
* - subagent_complete: subagent task completion events
|
|
80
|
+
* - error: session error events
|
|
81
|
+
* - permission: permission request events
|
|
82
|
+
* - question: question tool usage events
|
|
83
|
+
*/
|
|
84
|
+
export interface EventsConfig {
|
|
85
|
+
complete: boolean;
|
|
86
|
+
subagent_complete: boolean;
|
|
87
|
+
error: boolean;
|
|
88
|
+
permission: boolean;
|
|
89
|
+
question: boolean;
|
|
90
|
+
}
|
|
65
91
|
/**
|
|
66
92
|
* Top-level configuration object containing all service configs
|
|
67
93
|
*/
|
|
@@ -70,6 +96,8 @@ export interface EverynotifyConfig {
|
|
|
70
96
|
telegram: TelegramConfig;
|
|
71
97
|
slack: SlackConfig;
|
|
72
98
|
discord: DiscordConfig;
|
|
99
|
+
log: LogConfig;
|
|
100
|
+
events: EventsConfig;
|
|
73
101
|
}
|
|
74
102
|
/**
|
|
75
103
|
* Function signature for service send functions
|
package/package.json
CHANGED