SLOPSHOPPER

done-chime

長いターンが終わったときと、許可を待っているときに音とトーストで知らせる

newtoastaudio
v0.1.0MITupdated 2026-10-09yut0takagi/mods/done-chime
A shopper browsing a rack in a slop shop
README

mods

English | 日本語

A marketplace of mods for Claude Code. A mod is a plugin that adds features to Claude Code through function hooks.

ModWhat it does
branch-watchStops commits on a branch another session switched to, and lists each session's worktree and branch
push-gateHolds git push and similar commands until a person approves
auto-worktreeCreates a worktree on the first prompt and starts the work there
session-diffShows the files this session edited and their diffs
pr-paneShows open pull requests and their CI results
side-question/btw asks a side question without stopping the work
done-chimePlays a sound when a long turn ends and when Claude waits for permission
ja-checkTells you when a reply starts in English
usage-meterShows each turn's token counts and prompt cache rate

Install

claude plugin marketplace add yut0takagi/mods
claude plugin install branch-watch@yut0takagi-mods   # one line per mod you want

Developed with Claude Code 2.1.289. Mods are an early access feature, so a Claude Code update may break them. The VS Code and Cursor extension draws no mod UI, so some mods are used differently there.

Docs

To change a mod, read CONTRIBUTING. Changes are listed in the CHANGELOG. Report security problems as described in SECURITY.

License

MIT

Source 2 files
hooks/register.ts 35 lines
1import type { Register } from 'claude-code'
2
3import { durationText } from './time'
4
5export const register: Register = (on, options) => {
6  const minMs = (typeof options.minSeconds === 'number' ? options.minSeconds : 60) * 1_000
7  const hasSound = options.sound !== false
8
9  on('turn.complete', async ($, e, next) => {
10    const done = await next(e)
11
12    // 自分で中断したターンと、サブエージェントのターンは知らせない
13    if (e.agentId !== undefined || e.reason === 'aborted' || e.durationMs < minMs) {
14      return done
15    }
16
17    const took = durationText(e.durationMs)
18    $.ui.toast(e.reason === 'answer' ? `完了しました(${took})` : `途中で止まりました(${took})`, { timeoutMs: 10_000 })
19
20    if (hasSound) {
21      void $.audio.play({ asset: 'sounds/done.wav' }).catch(() => undefined)
22    }
23
24    return done
25  })
26
27  on('classic.Notification', async ($, e, next) => {
28    if (hasSound && e.notification_type === 'permission_prompt') {
29      void $.audio.play({ asset: 'sounds/ask.wav' }).catch(() => undefined)
30    }
31
32    return next(e)
33  })
34}
35
hooks/time.ts 8 lines
1/** かかった時間を「2分13秒」の形で。 */
2export function durationText(ms: number): string {
3  const seconds = Math.round(ms / 1_000)
4  const minutes = Math.floor(seconds / 60)
5
6  return minutes === 0 ? `${seconds}秒` : `${minutes}分${seconds % 60}秒`
7}
8