bobs-opencode-discord-notifier 0.1.3 → 0.1.4
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/dist/index.d.ts +5 -0
- package/{src/index.ts → dist/index.js} +38 -63
- package/package.json +15 -4
package/dist/index.d.ts
ADDED
|
@@ -1,72 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
interface DiscordWebhookConfig {
|
|
7
|
-
webhookUrl?: string;
|
|
8
|
-
enabled?: boolean;
|
|
9
|
-
username?: string;
|
|
10
|
-
avatarUrl?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const DiscordNotificationPlugin: Plugin = async ({ client, project }) => {
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var DiscordNotificationPlugin = async ({ client, project }) => {
|
|
14
6
|
return {
|
|
15
7
|
event: async ({ event }) => {
|
|
16
|
-
// 1. Handle Session Completed (Green)
|
|
17
8
|
if (event.type === "session.idle") {
|
|
18
9
|
await handleNotification(client, project, event, "idle");
|
|
19
|
-
}
|
|
20
|
-
// 2. Handle Permission Request (Orange)
|
|
21
|
-
else if ((event.type as string) === "permission.asked") {
|
|
10
|
+
} else if (event.type === "permission.asked") {
|
|
22
11
|
await handleNotification(client, project, event, "permission");
|
|
23
12
|
}
|
|
24
|
-
}
|
|
13
|
+
}
|
|
25
14
|
};
|
|
26
15
|
};
|
|
27
|
-
|
|
28
|
-
async function handleNotification(client: any, project: any, event: any, type: "idle" | "permission") {
|
|
16
|
+
async function handleNotification(client, project, event, type) {
|
|
29
17
|
try {
|
|
30
|
-
|
|
31
|
-
// Priority 1: project.config (opencode.json)
|
|
32
|
-
// Priority 2: Local file (for development or if schema is strict)
|
|
33
|
-
let config: DiscordWebhookConfig = project.config?.discordNotifications || {};
|
|
34
|
-
|
|
18
|
+
let config = project.config?.discordNotifications || {};
|
|
35
19
|
if (!config.webhookUrl) {
|
|
36
20
|
try {
|
|
37
21
|
const configPath = join(homedir(), ".config", "opencode", "discord-notification-config.json");
|
|
38
22
|
if (existsSync(configPath)) {
|
|
39
23
|
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
40
24
|
}
|
|
41
|
-
} catch (e) {
|
|
25
|
+
} catch (e) {
|
|
26
|
+
}
|
|
42
27
|
}
|
|
43
|
-
|
|
44
28
|
if (!config.enabled || !config.webhookUrl) return;
|
|
45
|
-
|
|
46
29
|
const sessionId = event.properties?.sessionID || event.properties?.id || event.properties?.sessionId;
|
|
47
30
|
if (!sessionId) return;
|
|
48
|
-
|
|
49
|
-
// Only wait on idle to give tokens time to settle
|
|
50
31
|
if (type === "idle") {
|
|
51
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
52
33
|
}
|
|
53
|
-
|
|
54
34
|
const [sRes, mRes] = await Promise.all([
|
|
55
35
|
client.session.get({ path: { id: sessionId } }),
|
|
56
36
|
client.session.messages({ path: { id: sessionId } })
|
|
57
37
|
]);
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const messages = (mRes as any).data || mRes || [];
|
|
61
|
-
|
|
38
|
+
const session = sRes.data || sRes;
|
|
39
|
+
const messages = mRes.data || mRes || [];
|
|
62
40
|
let lastText = "Response completed.";
|
|
63
41
|
let contextUsage = "N/A";
|
|
64
42
|
let modelName = session?.model?.name || "Unknown";
|
|
65
43
|
let totalTokensAccumulated = 0;
|
|
66
44
|
let pendingCommand = "";
|
|
67
|
-
|
|
68
|
-
// Analyze messages
|
|
69
|
-
messages.forEach((m: any) => {
|
|
45
|
+
messages.forEach((m) => {
|
|
70
46
|
const isAssistant = (m.info?.role || m.role) === "assistant";
|
|
71
47
|
if (isAssistant) {
|
|
72
48
|
const t = m.info?.tokens || m.tokens;
|
|
@@ -75,7 +51,7 @@ async function handleNotification(client: any, project: any, event: any, type: "
|
|
|
75
51
|
totalTokensAccumulated = Math.max(totalTokensAccumulated, turnTotal);
|
|
76
52
|
}
|
|
77
53
|
if (type === "permission" && m.parts) {
|
|
78
|
-
const toolPart = m.parts.find((p
|
|
54
|
+
const toolPart = m.parts.find((p) => p.type === "tool" && (p.state?.status === "pending" || p.state?.status === "running"));
|
|
79
55
|
if (toolPart) {
|
|
80
56
|
const input = toolPart.state?.input || {};
|
|
81
57
|
pendingCommand = input.command || input.filePath || JSON.stringify(input);
|
|
@@ -83,40 +59,36 @@ async function handleNotification(client: any, project: any, event: any, type: "
|
|
|
83
59
|
}
|
|
84
60
|
}
|
|
85
61
|
});
|
|
86
|
-
|
|
87
62
|
if (totalTokensAccumulated > 0 && session?.model?.limit?.context) {
|
|
88
|
-
const percentage = (
|
|
63
|
+
const percentage = (totalTokensAccumulated / session.model.limit.context * 100).toFixed(2);
|
|
89
64
|
contextUsage = `${percentage}%`;
|
|
90
65
|
}
|
|
91
|
-
|
|
92
|
-
const assistantMessages = messages.filter((m: any) => (m.info?.role || m.role) === "assistant");
|
|
66
|
+
const assistantMessages = messages.filter((m) => (m.info?.role || m.role) === "assistant");
|
|
93
67
|
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
94
68
|
if (lastAssistant) {
|
|
95
|
-
const text = lastAssistant.parts?.filter((p
|
|
69
|
+
const text = lastAssistant.parts?.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
96
70
|
if (text) lastText = text;
|
|
97
|
-
modelName =
|
|
71
|
+
modelName = lastAssistant.info?.modelID || lastAssistant.modelID || modelName;
|
|
98
72
|
}
|
|
99
|
-
|
|
100
73
|
const isPermission = type === "permission";
|
|
101
|
-
const title = isPermission ? "
|
|
102
|
-
const color = isPermission ?
|
|
103
|
-
|
|
74
|
+
const title = isPermission ? "\u26A0\uFE0F Permission Required" : "\u2705 Response Completed";
|
|
75
|
+
const color = isPermission ? 16753920 : 65280;
|
|
104
76
|
let description = lastText;
|
|
105
77
|
const fields = [
|
|
106
|
-
{ name: "
|
|
107
|
-
{ name: "
|
|
108
|
-
{ name: "
|
|
78
|
+
{ name: "\u{1F4CA} Context Usage", value: contextUsage, inline: true },
|
|
79
|
+
{ name: "\u{1F522} Total Tokens", value: `${totalTokensAccumulated.toLocaleString()} tokens`, inline: true },
|
|
80
|
+
{ name: "\u{1F916} Model", value: modelName, inline: true }
|
|
109
81
|
];
|
|
110
|
-
|
|
111
82
|
if (isPermission) {
|
|
112
|
-
fields.unshift({
|
|
113
|
-
name: "
|
|
114
|
-
value: `\`\`\`bash
|
|
115
|
-
|
|
83
|
+
fields.unshift({
|
|
84
|
+
name: "\u{1F512} Blocked Command / Action",
|
|
85
|
+
value: `\`\`\`bash
|
|
86
|
+
${pendingCommand || "Check terminal for details"}
|
|
87
|
+
\`\`\``,
|
|
88
|
+
inline: false
|
|
116
89
|
});
|
|
117
90
|
description = "OpenCode has paused execution and is waiting for you to authorize the operation shown above.";
|
|
118
91
|
}
|
|
119
|
-
|
|
120
92
|
await fetch(config.webhookUrl, {
|
|
121
93
|
method: "POST",
|
|
122
94
|
headers: { "Content-Type": "application/json" },
|
|
@@ -129,13 +101,16 @@ async function handleNotification(client: any, project: any, event: any, type: "
|
|
|
129
101
|
color,
|
|
130
102
|
fields,
|
|
131
103
|
footer: { text: `Session ID: ${sessionId}` },
|
|
132
|
-
timestamp: new Date().toISOString()
|
|
104
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
133
105
|
}]
|
|
134
|
-
})
|
|
106
|
+
})
|
|
135
107
|
});
|
|
136
108
|
} catch (e) {
|
|
137
109
|
console.error("Discord Plugin Error:", e);
|
|
138
110
|
}
|
|
139
111
|
}
|
|
140
|
-
|
|
141
|
-
export
|
|
112
|
+
var index_default = DiscordNotificationPlugin;
|
|
113
|
+
export {
|
|
114
|
+
DiscordNotificationPlugin,
|
|
115
|
+
index_default as default
|
|
116
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bobs-opencode-discord-notifier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "OpenCode plugin that sends Discord notifications on session completion and permission requests.",
|
|
5
|
-
"main": "src/index.ts",
|
|
6
|
-
"module": "src/index.ts",
|
|
7
5
|
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
8
14
|
"files": [
|
|
9
|
-
"
|
|
15
|
+
"dist",
|
|
10
16
|
"README.md"
|
|
11
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format esm --dts --out-dir dist --clean",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
12
22
|
"keywords": [
|
|
13
23
|
"opencode",
|
|
14
24
|
"plugin",
|
|
@@ -24,6 +34,7 @@
|
|
|
24
34
|
"devDependencies": {
|
|
25
35
|
"@opencode-ai/plugin": "latest",
|
|
26
36
|
"@types/node": "latest",
|
|
37
|
+
"tsup": "latest",
|
|
27
38
|
"typescript": "latest"
|
|
28
39
|
}
|
|
29
40
|
}
|