Toasts when a long turn finishes; /notify-test fires a sample toast.

Toasts when a long turn finishes; /notify-test fires a sample toast.
claude plugin marketplace add kenhuangus/claude-code-mods-pack
claude plugin install done-notify@kenhuang-modshooks/done-notify.mjs 35 lines1const NOTIFY = { plugin: "done-notify", key: "notify" };
2const LONG_MS = 3000;
3export function register(on) {
4 on("session.start", async ($, e, next) => {
5 const r = await next(e);
6 await $.state.set(NOTIFY, { count: 0 });
7 await $.command.register({ name: "notify-test", description: "Fire a sample completion toast" });
8 return r;
9 });
10 on("turn.complete", async ($, e, next) => {
11 const r = await next(e);
12 if (e.agentId) return r;
13 const ms = Number(e.durationMs ?? 0);
14 if (ms >= LONG_MS) {
15 const { value: n } = await $.state.get(NOTIFY);
16 await $.state.set(NOTIFY, { count: (n?.count ?? 0) + 1, lastMs: ms });
17 try { await $.ui.toast(`Turn done in ${Math.round(ms / 1000)}s`); } catch {}
18 }
19 return r;
20 });
21 on("command.run", { command: "notify-test" }, async ($) => {
22 const { value: n } = await $.state.get(NOTIFY);
23 await $.state.set(NOTIFY, { count: (n?.count ?? 0) + 1, lastMs: 0 });
24 try { await $.ui.toast("Notify test — Claude Code finished a long task"); } catch {}
25 return { text: "Notification fired." };
26 });
27 on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
28 if (e.props?.hasSurvey) return next(e);
29 const { value: n } = await $.state.get(NOTIFY);
30 if (!n?.count) return next(e);
31 const { Box, Text } = $.ui.resolve(e);
32 return Box({ paddingX: 1, children: [Text({ color: "green", children: `NOTIFY sent:${n.count}` })] });
33 });
34}
35types/index.d.ts 8 lines1declare module "claude-code" {
2 interface PluginState {
3 "done-notify": {
4 notify: { count: number; lastMs?: number };
5 };
6 }
7}
8