Skip to content

Instantly share code, notes, and snippets.

@maoyeedy
Last active May 6, 2026 10:57
Show Gist options
  • Select an option

  • Save maoyeedy/4c2726a6b87e28ff74e4603d7ae12ec7 to your computer and use it in GitHub Desktop.

Select an option

Save maoyeedy/4c2726a6b87e28ff74e4603d7ae12ec7 to your computer and use it in GitHub Desktop.
Toast when action complete with actual files modified. Windows only and requires toast.exe on path. https://github.com/shanselman/toasty
import type { Plugin } from "@opencode-ai/plugin";
// Plays a small toast when OpenCode goes idle.
// Customize the command, title, and messages with OPENCODE_BELL_* env vars.
declare const process: {
env: Record<string, string | undefined>;
};
type OpenCodeEvent = {
type?: string;
properties?: {
filesModified?: number | readonly unknown[];
files?: number | readonly unknown[];
};
};
type NotificationConfig = {
command: string;
title: string;
taskCompleteMessage: string;
idleMessage: string;
};
const DEFAULT_CONFIG: NotificationConfig = {
command: "toasty.exe",
title: "OpenCode",
taskCompleteMessage: "Task complete",
idleMessage: "Idle prompt",
};
function readNotificationConfig(): NotificationConfig {
const env = process.env;
return {
command: env.OPENCODE_BELL_COMMAND?.trim() || DEFAULT_CONFIG.command,
title: env.OPENCODE_BELL_TITLE?.trim() || DEFAULT_CONFIG.title,
taskCompleteMessage:
env.OPENCODE_BELL_TASK_COMPLETE_MESSAGE?.trim() || DEFAULT_CONFIG.taskCompleteMessage,
idleMessage: env.OPENCODE_BELL_IDLE_MESSAGE?.trim() || DEFAULT_CONFIG.idleMessage,
};
}
function hasChanges(value: number | readonly unknown[] | undefined): boolean {
if (typeof value === "number") {
return value > 0;
}
return Array.isArray(value) && value.length > 0;
}
function selectNotificationMessage(
files: number | readonly unknown[] | undefined,
config: NotificationConfig,
): string {
return hasChanges(files) ? config.taskCompleteMessage : config.idleMessage;
}
export const OpenCodeToast: Plugin = async ({ $ }) => {
const config = readNotificationConfig();
return {
event: async ({ event }) => {
const e = event as OpenCodeEvent;
if (e.type !== "session.idle") {
return;
}
const props = e.properties ?? {};
const files = props.filesModified ?? props.files;
const message = selectNotificationMessage(files, config);
await $`${config.command} ${message} -t ${config.title}`.quiet().nothrow();
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment