Last active
March 4, 2026 11:35
-
-
Save shmileee/f8b9d0e380a53055e14fe6403c86e2cf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { Plugin } from "@opencode-ai/plugin"; | |
| const NOTIFICATION_TITLE = "opencode"; | |
| const TMUX_WAITING_OPTION = "@opencode_waiting"; | |
| const TMUX_WAITING_SYMBOL = "●"; | |
| export const TmuxWindowNotificationPlugin: Plugin = async ({ $ }) => { | |
| const getPaneFromEnv = async (): Promise<string | null> => { | |
| try { | |
| const result = await $`printenv TMUX_PANE`.text(); | |
| const pane = result.trim(); | |
| return pane.length > 0 ? pane : null; | |
| } catch { | |
| return null; | |
| } | |
| }; | |
| const tmuxPane = await getPaneFromEnv(); | |
| const getWindowLabel = async ( | |
| tmuxPane: string | null, | |
| ): Promise<string | null> => { | |
| if (!tmuxPane) return null; | |
| try { | |
| const result = | |
| await $`tmux display-message -p -t ${tmuxPane} '#S:#I #{window_name}'`.text(); | |
| const label = result.trim(); | |
| return label.length > 0 ? label : null; | |
| } catch { | |
| return null; | |
| } | |
| }; | |
| const getWindowId = async ( | |
| tmuxPane: string | null, | |
| ): Promise<string | null> => { | |
| if (!tmuxPane) return null; | |
| try { | |
| const result = | |
| await $`tmux display-message -p -t ${tmuxPane} '#{window_id}'`.text(); | |
| const windowId = result.trim(); | |
| return windowId.length > 0 ? windowId : null; | |
| } catch { | |
| return null; | |
| } | |
| }; | |
| const isAlacrittyFocused = async (): Promise<boolean> => { | |
| try { | |
| const result = | |
| await $`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`.text(); | |
| return /alacritty/i.test(result.trim()); | |
| } catch { | |
| return false; | |
| } | |
| }; | |
| const notify = async (message: string): Promise<void> => { | |
| await $`osascript -e 'on run argv' -e 'display notification (item 1 of argv) with title (item 2 of argv) sound name "Pop"' -e 'end run' ${message} ${NOTIFICATION_TITLE}`; | |
| }; | |
| let waitingState: boolean | null = null; | |
| const setWaitingIndicator = async ( | |
| tmuxWindowId: string | null, | |
| waiting: boolean, | |
| force: boolean = false, | |
| ): Promise<void> => { | |
| if (!tmuxWindowId) return; | |
| if (!force && waitingState === waiting) return; | |
| try { | |
| if (waiting) { | |
| await $`tmux set-window-option -q -t ${tmuxWindowId} ${TMUX_WAITING_OPTION} ${TMUX_WAITING_SYMBOL}`; | |
| } else { | |
| await $`tmux set-window-option -q -u -t ${tmuxWindowId} ${TMUX_WAITING_OPTION}`; | |
| } | |
| waitingState = waiting; | |
| } catch { | |
| // Ignore tmux update failures. | |
| } | |
| }; | |
| const tmuxWindowId = await getWindowId(tmuxPane); | |
| await setWaitingIndicator(tmuxWindowId, false, true); | |
| return { | |
| event: async ({ event }) => { | |
| if (event.type === "session.status") { | |
| const status = event.properties?.status; | |
| const statusType = typeof status === "object" ? status?.type : status; | |
| if (statusType === "busy") { | |
| await setWaitingIndicator(tmuxWindowId, false); | |
| } | |
| return; | |
| } | |
| if (event.type === "permission.updated") { | |
| await setWaitingIndicator(tmuxWindowId, true); | |
| return; | |
| } | |
| if (event.type !== "session.idle") return; | |
| await setWaitingIndicator(tmuxWindowId, true); | |
| const [windowLabel, alacrittyFocused] = await Promise.all([ | |
| getWindowLabel(tmuxPane), | |
| isAlacrittyFocused(), | |
| ]); | |
| if (alacrittyFocused) return; | |
| const message = windowLabel | |
| ? `Waiting for input in ${windowLabel}` | |
| : "Waiting for your input"; | |
| await notify(message); | |
| }, | |
| "permission.ask": async () => { | |
| await setWaitingIndicator(tmuxWindowId, true); | |
| }, | |
| "chat.message": async () => { | |
| await setWaitingIndicator(tmuxWindowId, false); | |
| }, | |
| }; | |
| }; | |
| export default TmuxWindowNotificationPlugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment