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.
@@ -0,0 +1,5 @@
1
+ import { Plugin } from '@opencode-ai/plugin';
2
+
3
+ declare const DiscordNotificationPlugin: Plugin;
4
+
5
+ export { DiscordNotificationPlugin, DiscordNotificationPlugin as default };
@@ -1,72 +1,48 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import type { Plugin } from "@opencode-ai/plugin";
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
- // 1. GET CONFIGURATION
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 session = (sRes as any).data || sRes;
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: any) => p.type === "tool" && (p.state?.status === "pending" || p.state?.status === "running"));
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 = ((totalTokensAccumulated / session.model.limit.context) * 100).toFixed(2);
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: any) => p.type === "text").map((p: any) => p.text).join("\n");
69
+ const text = lastAssistant.parts?.filter((p) => p.type === "text").map((p) => p.text).join("\n");
96
70
  if (text) lastText = text;
97
- modelName = (lastAssistant.info?.modelID || lastAssistant.modelID) || modelName;
71
+ modelName = lastAssistant.info?.modelID || lastAssistant.modelID || modelName;
98
72
  }
99
-
100
73
  const isPermission = type === "permission";
101
- const title = isPermission ? "⚠️ Permission Required" : " Response Completed";
102
- const color = isPermission ? 0xffa500 : 0x00ff00;
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: "📊 Context Usage", value: contextUsage, inline: true },
107
- { name: "🔢 Total Tokens", value: `${totalTokensAccumulated.toLocaleString()} tokens`, inline: true },
108
- { name: "🤖 Model", value: modelName, inline: true }
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: "🔒 Blocked Command / Action",
114
- value: `\`\`\`bash\n${pendingCommand || "Check terminal for details"}\n\`\`\``,
115
- inline: false
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 default DiscordNotificationPlugin;
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",
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
- "src",
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
  }