SLOPSHOPPER

push-to-talk

決めたキーを押している間だけ録音し、話した内容を入力欄に入れる

newtoaststatusprocesstimer
v0.3.0no licenseupdated 2026-10-23kokotosu/claude-mods/push-to-talk
A shopper browsing a rack in a slop shop
README

claude-mods

kokot が作った Claude Code 用の mod 集です。

modできること
usage-band入力欄の上に、残りの使用量(5時間枠・週枠・コンテキスト)とリセットまでの時間を帯で表示する。7日間のトークンを何に使ったかの円グラフも出す。チャット名の色分けもする
chat-timeLINE のように、各メッセージの下に送った日時を表示する
ja-questionsClaude からの質問が英語なら、日本語に訳して表示する
push-to-talk決めたキーを押している間だけ録音して、話した内容を入力欄に入れる
shield危ない操作(フォルダごと削除・履歴の上書き・秘密ファイルの書き換えなど)は止めて確認し、Claude が書いたファイルは自動で構文チェックする。ほかの作業中のチャットも表示し、名前を押すとそのチャットへ飛べる。守るもの・守れないものは shield/SAFETY.md

別のパソコンに入れる方法

ログインはいりません。ほしい mod だけ選んで入れられます。

  1. ターミナルで、まず棚の登録を打つ(1回だけ)
  2. そのあと、ほしい mod の行だけ打つ
claude plugin marketplace add kokotosu/claude-mods
claude plugin install usage-band@kokot-claude-mods
claude plugin install chat-time@kokot-claude-mods
claude plugin install ja-questions@kokot-claude-mods
claude plugin install push-to-talk@kokot-claude-mods
claude plugin install shield@kokot-claude-mods
  1. Claude を開き直す

Claude に頼むなら「GitHub の kokotosu/claude-mods から chat-time だけ入れて」のように言えば入れてくれる。

更新したとき

claude plugin marketplace update kokot-claude-mods

そのあと claude plugin update <mod名>@kokot-claude-mods を打つ。

注意

  • chat-time と usage-band の円グラフは Node.js が要る
  • push-to-talk は Windows 専用
  • shield のチャットへ飛ぶボタンは、パソコンの Claude アプリで押したときだけ効く
  • チャット名の色分けを前のパソコンと同じにしたいときは、~/.claude/mods/chat-colors.json もコピーする
Source 1 files
hooks/register.ts 94 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// The key is watched by helper/ptt.ps1, which drives the app's own voice
4// input; this module starts it and shows where it stands.
5// One helper runs for every session: it writes the time to
6// %TEMP%\push-to-talk.alive every 3 seconds, and each session looks at that
7// file every 15 and starts a helper only once it goes stale. A helper that
8// finds another one running answers BUSY and exits.
9// A reload of this module restarts the helper, so an edit to ptt.ps1 alone
10// takes effect only with a real change here too: a reload follows content,
11// and an edit undone before the turn ends reloads nothing.
12// The helper writes what it saw to %TEMP%\push-to-talk-diag.txt when it
13// cannot find the button.
14const CHECK_MS = 15_000
15const STALE_MS = 10_000
16
17let isRunning = false
18
19async function isAnotherAlive($: EngineInterface, heartbeat: string) {
20  try {
21    const at = Number(await $.fs.read(heartbeat))
22    return (await $.clock.now()) - at < STALE_MS
23  } catch {
24    return false
25  }
26}
27
28// Resolves once the helper exits: true when it ran here, false on BUSY.
29async function runHelper($: EngineInterface, key: string, idle: string) {
30  const helper = $.process.spawn({
31    argv: [
32      'powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass',
33      '-File', `${$.plugin.root}/helper/ptt.ps1`, '-Key', key,
34    ],
35  })
36
37  let hasRun = false
38  let pending = ''
39  for await (const { stream, text } of helper) {
40    if (stream === 'stderr') {
41      $.ui.log(text, { to: 'debug' })
42      continue
43    }
44    pending += text
45    const lines = pending.split(/\r?\n/)
46    pending = lines.pop() ?? ''
47
48    for (const line of lines) {
49      if (line === 'READY') hasRun = true
50      if (line === 'REC') $.ui.status('🎙 録音中')
51      else if (line === 'READY' || line === 'DONE') $.ui.status(idle)
52      else if (line === 'NOBUTTON') $.ui.toast('録音ボタンが見つかりませんでした Claudeの入力欄を表示してから押してください')
53      else if (line.startsWith('ERR ')) {
54        $.ui.status(idle)
55        $.ui.toast(`音声入力のエラー:${line.slice(4)}`)
56      }
57    }
58  }
59  return hasRun
60}
61
62async function ensureHelper($: EngineInterface, heartbeat: string, key: string, idle: string) {
63  if (isRunning) return
64  if (await isAnotherAlive($, heartbeat)) {
65    $.ui.status(idle)
66    return
67  }
68  isRunning = true
69  try {
70    const hasRun = await runHelper($, key, idle)
71    if (hasRun) $.ui.toast('音声入力の補助プログラムが止まりました 15秒以内に起動し直します')
72  } finally {
73    isRunning = false
74  }
75}
76
77export const register: Register = (on, options) => {
78  const key = typeof options.key === 'string' ? options.key : 'RightCtrl'
79  const idle = `${key} 長押しで音声入力`
80
81  on('session.start', async ($, e, next) => {
82    const started = await next(e)
83
84    const temp = await $.env.get('TEMP')
85    if (temp === undefined) return started
86    const heartbeat = `${temp}/push-to-talk.alive`
87
88    void ensureHelper($, heartbeat, key, idle)
89    $.clock.every(CHECK_MS, () => void ensureHelper($, heartbeat, key, idle))
90
91    return started
92  })
93}
94