Skip to content

Instantly share code, notes, and snippets.

@shmileee
Last active March 4, 2026 11:35
Show Gist options
  • Select an option

  • Save shmileee/f8b9d0e380a53055e14fe6403c86e2cf to your computer and use it in GitHub Desktop.

Select an option

Save shmileee/f8b9d0e380a53055e14fe6403c86e2cf to your computer and use it in GitHub Desktop.
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