SLOPSHOPPER

done-notify

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

newbandcommandtoast
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · done-notify
› fix the failing auth test and add an audit log call ╭──────────────────╮ │ done-notify │ ⏺ Read(src/auth.ts) │ Turn done in 42s │ ⎿ Read 6 lines ╰──────────────────╯ ⏺ Update(src/auth.ts) ╭────────────────────────────────────────────╮ ⎿ Added 2 lines, removed 1 line │ done-notify │ ⏺ Bash(bun test) │ Notify test — Claude Code finished a long │ ⎿ 3 pass, 1 fail │ task │ ╰────────────────────────────────────────────╯ ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /notify-test ⎿ done-notify: Notification fired. NOTIFY sent:2 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
NOTIFY sent:2
README

done-notify

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

Install

claude plugin marketplace add kenhuangus/claude-code-mods-pack
claude plugin install done-notify@kenhuang-mods
Source 2 files
hooks/done-notify.mjs 35 lines
1const 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}
35
types/index.d.ts 8 lines
1declare module "claude-code" {
2  interface PluginState {
3    "done-notify": {
4      notify: { count: number; lastMs?: number };
5    };
6  }
7}
8