zjbar-opencode 0.1.0 → 0.1.1

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 (2) hide show
  1. package/dist/index.js +117 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,11 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/index.ts
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { homedir, platform } from "node:os";
8
+ import { spawn, execFile as execFileCb } from "node:child_process";
2
9
  var TOOL_MAP = {
3
10
  bash: "Bash",
4
11
  read: "Read",
@@ -14,14 +21,100 @@ var TOOL_MAP = {
14
21
  function capitalize(s) {
15
22
  return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
16
23
  }
17
- var ZjbarPlugin = async ({ $, directory }) => {
24
+ var DEFAULT_NOTIFY_EVENTS = ["PermissionRequest", "Notification", "Stop"];
25
+ var NOTIFY_RATE_LIMIT_SECS = 10;
26
+ var lastNotifyTime = {};
27
+ function loadSettings() {
28
+ const settingsPath = join(homedir(), ".config", "zellij", "plugins", "zjbar.json");
29
+ try {
30
+ if (existsSync(settingsPath)) {
31
+ return JSON.parse(readFileSync(settingsPath, "utf-8"));
32
+ }
33
+ } catch {}
34
+ return {};
35
+ }
36
+ function shouldNotify(hookEvent, paneId, termProgram) {
37
+ const settings = loadSettings();
38
+ const mode = settings.notifications ?? "always";
39
+ if (mode === "off")
40
+ return false;
41
+ const events = settings.notify_events ?? DEFAULT_NOTIFY_EVENTS;
42
+ if (!events.includes(hookEvent))
43
+ return false;
44
+ const now = Math.floor(Date.now() / 1000);
45
+ const key = `pane-${paneId}`;
46
+ if (now - (lastNotifyTime[key] ?? 0) < NOTIFY_RATE_LIMIT_SECS)
47
+ return false;
48
+ lastNotifyTime[key] = now;
49
+ if (mode === "unfocused" && platform() === "darwin" && termProgram) {
50
+ try {
51
+ const { execSync } = __require("node:child_process");
52
+ const frontApp = execSync(`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`, { encoding: "utf-8" }).trim();
53
+ let expected = termProgram;
54
+ if (expected === "Apple_Terminal")
55
+ expected = "Terminal";
56
+ if (expected === "iTerm.app")
57
+ expected = "iTerm2";
58
+ if (frontApp === expected)
59
+ return false;
60
+ } catch {}
61
+ }
62
+ return true;
63
+ }
64
+ function sendNotification(hookEvent, paneId, zellijSession, termProgram) {
65
+ const appName = "OpenCode";
66
+ const iconFile = "opencode-logo.png";
67
+ const pluginDir = join(homedir(), ".config", "zellij", "plugins");
68
+ const iconPath = join(pluginDir, iconFile);
69
+ let title;
70
+ let message;
71
+ switch (hookEvent) {
72
+ case "PermissionRequest":
73
+ title = `⚠ ${appName}`;
74
+ message = "Permission requested";
75
+ break;
76
+ case "Stop":
77
+ title = `✅ ${appName}`;
78
+ message = "Task completed";
79
+ break;
80
+ case "Notification":
81
+ title = appName;
82
+ message = "Notification received";
83
+ break;
84
+ default:
85
+ title = appName;
86
+ message = `Event: ${hookEvent}`;
87
+ break;
88
+ }
89
+ if (platform() === "darwin") {
90
+ if (hookEvent === "PermissionRequest") {
91
+ process.stdout.write("\x07");
92
+ }
93
+ const focusCmd = termProgram ? `open -a '${termProgram}' && zellij -s '${zellijSession}' pipe --name zjbar:focus -- ${paneId}` : `zellij -s '${zellijSession}' pipe --name zjbar:focus -- ${paneId}`;
94
+ const args = ["-title", title, "-message", message, "-execute", focusCmd];
95
+ if (existsSync(iconPath)) {
96
+ args.unshift("-contentImage", iconPath);
97
+ }
98
+ execFileCb("terminal-notifier", args, (err) => {
99
+ if (err) {
100
+ execFileCb("osascript", [
101
+ "-e",
102
+ `display notification "${message.replace(/"/g, "\\\"")}" with title "${title.replace(/"/g, "\\\"")}"`
103
+ ]);
104
+ }
105
+ });
106
+ } else if (platform() === "linux") {
107
+ execFileCb("notify-send", [title, message]);
108
+ }
109
+ }
110
+ var ZjbarPlugin = async ({ directory }) => {
18
111
  const zellijSession = process.env.ZELLIJ_SESSION_NAME;
19
112
  const paneId = process.env.ZELLIJ_PANE_ID;
20
113
  if (!zellijSession || !paneId)
21
114
  return {};
22
115
  const sessionId = crypto.randomUUID();
23
116
  const termProgram = process.env.TERM_PROGRAM || null;
24
- async function sendToZjbar(hookEvent, toolName) {
117
+ function sendToZjbar(hookEvent, toolName) {
25
118
  const payload = JSON.stringify({
26
119
  source: "opencode",
27
120
  pane_id: parseInt(paneId, 10),
@@ -32,37 +125,47 @@ var ZjbarPlugin = async ({ $, directory }) => {
32
125
  zellij_session: zellijSession,
33
126
  term_program: termProgram
34
127
  });
35
- try {
36
- await $`zellij -s ${zellijSession} pipe --name zjbar -- ${payload}`.quiet();
37
- } catch {}
128
+ const child = spawn("zellij", [
129
+ "-s",
130
+ zellijSession,
131
+ "pipe",
132
+ "--name",
133
+ "zjbar",
134
+ "--",
135
+ payload
136
+ ], { detached: true, stdio: "ignore" });
137
+ child.unref();
138
+ if (shouldNotify(hookEvent, paneId, termProgram)) {
139
+ sendNotification(hookEvent, paneId, zellijSession, termProgram);
140
+ }
38
141
  }
39
- await sendToZjbar("SessionStart");
142
+ sendToZjbar("SessionStart");
40
143
  return {
41
144
  event: async ({ event }) => {
42
145
  switch (event.type) {
43
146
  case "session.created":
44
- await sendToZjbar("SessionStart");
147
+ sendToZjbar("SessionStart");
45
148
  break;
46
149
  case "session.idle":
47
- await sendToZjbar("Stop");
150
+ sendToZjbar("Stop");
48
151
  break;
49
152
  case "session.deleted":
50
- await sendToZjbar("SessionEnd");
153
+ sendToZjbar("SessionEnd");
51
154
  break;
52
155
  case "permission.asked":
53
- await sendToZjbar("PermissionRequest");
156
+ sendToZjbar("PermissionRequest");
54
157
  break;
55
- case "message.updated":
56
- await sendToZjbar("PostToolUse");
158
+ case "message.created":
159
+ sendToZjbar("UserPromptSubmit");
57
160
  break;
58
161
  }
59
162
  },
60
163
  "tool.execute.before": async (input) => {
61
164
  const toolName = TOOL_MAP[input.tool] || capitalize(input.tool);
62
- await sendToZjbar("PreToolUse", toolName);
165
+ sendToZjbar("PreToolUse", toolName);
63
166
  },
64
167
  "tool.execute.after": async () => {
65
- await sendToZjbar("PostToolUse");
168
+ sendToZjbar("PostToolUse");
66
169
  }
67
170
  };
68
171
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zjbar-opencode",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "zjbar plugin for OpenCode — live AI activity indicators in Zellij status bar",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",