ai-notify 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md ADDED
@@ -0,0 +1,121 @@
1
+ # ai-notify
2
+
3
+ [English](README.md) · **日本語**
4
+
5
+ **ターミナルのAIエージェントが「あなたを必要とした瞬間」を逃さない** — Claude Code・Codex などのエージェントがターンを終えた/入力を求めた瞬間に、音・読み上げ・デスクトップ通知で知らせます。**全エージェント・全ターミナルを1つのスイッチで一括ミュート**。デーモンも常駐プロセスも無し。
6
+
7
+ ![ai-notify demo](https://raw.githubusercontent.com/unoryota/ai-notify/main/assets/hero-ja.gif)
8
+
9
+ ```sh
10
+ npm i -g ai-notify
11
+ ai-notify init # インストール済みのエージェントを自動検出して配線
12
+ ```
13
+
14
+ ## 何が違うか
15
+
16
+ エージェントは数分間も沈黙しがち。ai-notify は適切な瞬間に呼び戻します。とくに **AIを並列でたくさん動かす運用**のために作られています:
17
+
18
+ - 🎙️ **ターミナルごとに声を変えられる。** ペインごとに別の声を割り当てれば、**どの窓が終わったか**を聞くだけで分かります — `export AI_NOTIFY_VOICE=Eddy`(または [VOICEVOX](#-voicevox-キャラクターボイス) のキャラ)。
19
+ - 🌐 **あなたの言語で読み上げ。** エージェントの英語の返信・プロンプトを翻訳してから読み上げ/表示(キーレス・無料)。日本人にうれしい。
20
+ - 📝 **「何をしたか」を教える。** 完了通知は「終わりました」だけでなく、エージェントの最後の返信(transcript)から作業内容を要約します。
21
+ - 🔕 **1スイッチで全部ミュート。** 全エージェント・全ターミナルが同じフラグを読むので、会議中はワンタップで全部静かに。
22
+ - 🔔 **ネイティブのメニューバーも内蔵。** `ai-notify menubar install` — Hammerspoon/SwiftBar 不要。
23
+
24
+ ## 対応エージェント
25
+
26
+ | エージェント | 状態 | 配線方法 |
27
+ | ----- | ------ | -------------- |
28
+ | Claude Code | ✅ | `~/.claude/settings.json` の `Notification` + `Stop` フック |
29
+ | Codex CLI | ✅ | `~/.codex/config.toml` の `notify`(`agent-turn-complete`) |
30
+ | Gemini CLI | 🧪 検出のみ・フック作業中 | PR歓迎 |
31
+
32
+ 別のエージェント(aider, opencode, amp …)の追加は小さなPRで可能:`src/providers/` にファイルを1つ置くだけ。[CONTRIBUTING](CONTRIBUTING.md) 参照。
33
+
34
+ ## コマンド
35
+
36
+ ```sh
37
+ ai-notify init [--dry-run] [--only claude,codex] # 検出したエージェントを配線
38
+ ai-notify toggle | on | off | status # ミュートスイッチ
39
+ ai-notify volume [0.0-2.0] # 音量の取得/設定
40
+ ai-notify voice [number|name|preview|default] # 読み上げ音声を選ぶ
41
+ ai-notify voicevox [on <id>|off|speakers|test] # VOICEVOXの声で読み上げ
42
+ ai-notify translate [on <lang>|off|test] # エージェントの文章を自分の言語で
43
+ ai-notify menubar [install|uninstall|status] # ネイティブのメニューバー(macOS)
44
+ ai-notify doctor # 依存・配線の確認
45
+ ai-notify uninstall # 配線をきれいに削除
46
+ ```
47
+
48
+ ペイン別の上書き — エージェントを起動する**前**に、その端末で `export` する:
49
+
50
+ ```sh
51
+ AI_NOTIFY_LABEL=api # この窓の読み上げ/通知での名前
52
+ AI_NOTIFY_VOICE=Eddy # この窓の `say` 音声
53
+ AI_NOTIFY_VOICEVOX_SPEAKER=3 # この窓の VOICEVOX 話者ID
54
+ AI_NOTIFY_VOLUME=0.5 # この窓の音量(0.0〜2.0)
55
+ ```
56
+
57
+ ## 🎛️ ネイティブのメニューバー — ミュート・音量・声
58
+
59
+ エージェントが走っているターミナルにはコマンドを打てないので、**メニューバー**から全部操作します:
60
+
61
+ ```sh
62
+ ai-notify menubar install # ネイティブのメニューバーアプリ・ログイン時に自動起動
63
+ ```
64
+
65
+ モノクロの波形アイコンが**状態を色で**表します(Adobe風):通常はシルエットのみ、入力待ちがあると**黄ドット**、ミュート中は**赤+斜線**。
66
+
67
+ - **左クリック** → メニュー:**音量スライダー**、**声の一覧**(システム+VOICEVOX)、**ペイン別**設定(開いている各ターミナルに個別の声と音量)。
68
+ - **右クリック** → 即ミュート切替。
69
+
70
+ 第三者アプリ不要。別の方法が好みなら、**Hammerspoon**・**SwiftBar/xbar**・**Raycast**・標準の**ショートカット**用レシピが [`recipes/`](recipes/) にあります。`ai-notify status --icon` は `🔔`/`🔕` だけを出力するので、tmux・プロンプト・Claude Code のステータスラインに埋め込めます。
71
+
72
+ > 切替は実行中でも効きます:次にエージェントが発火した時にフラグを読むので、トグルした瞬間に全稼働エージェントへ反映されます。
73
+
74
+ ## 🎙️ VOICEVOX キャラクターボイス
75
+
76
+ 通知を [VOICEVOX](https://voicevox.hiroshiba.jp/) のキャラ声(例:ずんだもん)で読み上げられます(無料・ローカル・オフライン)。
77
+
78
+ > **VOICEVOXアプリのインストールと起動が必要です。** ai-notify はローカルのエンジンを叩くだけで、音声データは同梱していません。未起動なら ai-notify は**OS標準の音声**(Samantha, Kyoko …)を使います(こちらは設定不要ですぐ動きます)。
79
+
80
+ `ai-notify voicevox setup` が案内します — ダウンロードページを開き、インストール済みならアプリを起動してエンジンの起動を待ちます。その後:
81
+
82
+ ```sh
83
+ ai-notify voicevox setup # VOICEVOXの導入/起動
84
+ ai-notify voicevox speakers # 利用可能なキャラとIDの一覧
85
+ ai-notify voicevox on 3 # 話者3(例:ずんだもん)を使う
86
+ ```
87
+
88
+ `AI_NOTIFY_VOICEVOX_SPEAKER` で各ペインに別キャラを割り当て可能。エンジンが起動していなければ自動でOS音声にフォールバックします。
89
+ *VOICEVOXのキャラには利用規約があります。録画などを共有する場合は [VOICEVOXのガイドライン](https://voicevox.hiroshiba.jp/term/) に従ってクレジットしてください。*
90
+
91
+ ## 🌐 あなたの言語で読み上げ
92
+
93
+ ```sh
94
+ ai-notify translate on ja # エージェントのメッセージを翻訳してから読み上げ
95
+ ai-notify translate test "I fixed the auth bug and added 3 tests."
96
+ ```
97
+
98
+ キーレス・無料(HTTP 1リクエスト。オフライン時はローカルの定型文にフォールバック)。デスクトップ通知には原文も表示されます。
99
+
100
+ ## ⏳ どの窓が・何を求めているか
101
+
102
+ 各通知のタイトルに窓ラベルが付きます — 入力待ちは `⏳ <label>`、完了は `✓ <label>`。本文には**何を**(翻訳されたプロンプト、または作業内容の要約)が出ます。各ペインに短い `AI_NOTIFY_LABEL` を設定すれば、10個のターミナルもひと目で見分けられます。
103
+
104
+ ## 仕組み
105
+
106
+ XDGパス配下の単一のミュートフラグと設定だけ — デーモンも調整も無し:
107
+
108
+ ```
109
+ ${XDG_STATE_HOME:-~/.local/state}/ai-notify/muted # 存在=ミュート
110
+ ${XDG_CONFIG_HOME:-~/.config}/ai-notify/config.json # 音・声・各種オプション
111
+ ```
112
+
113
+ 各エージェントのフックが `ai-notify hook --source <agent>` を呼び、発火時にこの1つのフラグを読みます。`ai-notify config init` で編集可能な設定(エージェント別の音・声・TTSバックエンド・翻訳・テンプレート)を書き出せます。
114
+
115
+ ## 対応プラットフォーム
116
+
117
+ macOS は完全対応(`afplay` / `say` / VOICEVOX / `terminal-notifier` / ネイティブメニューバー)。Linux はベストエフォート(`paplay`/`canberra`, `notify-send`, `spd-say`/`espeak`, VOICEVOX)。Windows はビープ+PowerShell読み上げ。利用できないバックエンドは静かに縮退し、エラーにはなりません。
118
+
119
+ ## ライセンス
120
+
121
+ [MIT](LICENSE)。ランタイム依存ゼロ。
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # ai-notify
2
2
 
3
+ **English** · [日本語](README.ja.md)
4
+
3
5
  **Know the moment your terminal AI agent needs you** — a sound, a spoken read-out, and a desktop banner the instant Claude Code, Codex, or another agent finishes a turn or asks for input. One mute switch covers **all of them, across every terminal**. No daemon, no background process.
4
6
 
5
- ![ai-notify demo](https://raw.githubusercontent.com/unoryota/ai-notify/main/assets/demo.gif)
7
+ ![ai-notify demo](https://raw.githubusercontent.com/unoryota/ai-notify/main/assets/hero-en.gif)
6
8
 
7
9
  ```sh
8
10
  npm i -g ai-notify
@@ -19,10 +21,6 @@ Plenty of agents go quiet for minutes. ai-notify pulls you back at the right mom
19
21
  - 🔕 **One switch mutes everything.** Every agent in every terminal reads the same flag — one tap silences them all for a meeting.
20
22
  - 🔔 **A real menu bar bell, built in.** `ai-notify menubar install` — no Hammerspoon/SwiftBar required.
21
23
 
22
- > ### 日本語
23
- > 複数のAIエージェント(Claude Code / Codex …)を**並列で動かすと、どのターミナルの通知か分からない**——を解決する通知ツール。
24
- > **ペインごとに声を変えられる**(VOICEVOXのキャラ声も)/**英語の出力を日本語に翻訳して読み上げ**/**完了通知に作業内容の要約**/**1タップで全部ミュート**(MTG用)/**メニューバーのベルも内蔵**。
25
-
26
24
  ## Supported agents
27
25
 
28
26
  | Agent | Status | How it's wired |
@@ -75,9 +73,14 @@ No third-party app needed. Prefer something else? There are drop-in recipes for
75
73
 
76
74
  ## 🎙️ VOICEVOX character voices
77
75
 
78
- Speak your notifications in [VOICEVOX](https://voicevox.hiroshiba.jp/) character voices (free, local, offline). Run the VOICEVOX app, then:
76
+ Optionally speak your notifications in [VOICEVOX](https://voicevox.hiroshiba.jp/) character voices (e.g. ずんだもん) — free, local, offline.
77
+
78
+ > **Needs the VOICEVOX app installed and running.** ai-notify calls its local engine; it does not bundle the voices. Without it, ai-notify just uses your OS voice (Samantha, Kyoko, …) — no setup required.
79
+
80
+ `ai-notify voicevox setup` walks you through it — it opens the download page, or launches the app and waits for the engine if it's already installed. Then:
79
81
 
80
82
  ```sh
83
+ ai-notify voicevox setup # install / launch VOICEVOX
81
84
  ai-notify voicevox speakers # list available characters + ids
82
85
  ai-notify voicevox on 3 # use speaker 3 (e.g. ずんだもん)
83
86
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-notify",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Desktop, sound, and spoken notifications for terminal AI coding agents (Claude Code, Codex, Gemini, ...) — with one mute switch that covers all of them, across every terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "menubar/README.md",
16
16
  "menubar/dist",
17
17
  "README.md",
18
+ "README.ja.md",
18
19
  "LICENSE"
19
20
  ],
20
21
  "scripts": {
package/src/cli.mjs CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  updatePaneSetting,
28
28
  } from './state.mjs';
29
29
 
30
- const VERSION = '0.2.1';
30
+ const VERSION = '0.2.2';
31
31
 
32
32
  const args = process.argv.slice(2);
33
33
  const cmd = args[0];
@@ -253,6 +253,33 @@ const cmds = {
253
253
  const config = readConfig();
254
254
  const url = config.voicevox?.url || voicevox.DEFAULT_URL;
255
255
 
256
+ if (sub === 'setup') {
257
+ if (voicevox.isAvailable(url)) {
258
+ log('✓ VOICEVOX engine is already running.');
259
+ return log('Enable it: ai-notify voicevox on (list voices: ai-notify voicevox speakers)');
260
+ }
261
+ if (process.platform !== 'darwin') {
262
+ log(`VOICEVOX is not running. Install it from ${voicevox.DOWNLOAD_URL} and launch the app, then:`);
263
+ return log(' ai-notify voicevox on');
264
+ }
265
+ if (voicevox.appInstalled()) {
266
+ log('VOICEVOX is installed but not running. Launching it…');
267
+ voicevox.launchApp();
268
+ log(' Waiting for the engine to start (first launch can take ~30s)…');
269
+ if (voicevox.waitForEngine(url, 45000)) {
270
+ log('✓ engine ready.');
271
+ return log('Enable it: ai-notify voicevox on');
272
+ }
273
+ return log(' Still starting. Once VOICEVOX is open, run: ai-notify voicevox on');
274
+ }
275
+ log('VOICEVOX is not installed. Opening the download page…');
276
+ voicevox.openDownloadPage();
277
+ log(' 1. Download the macOS app and move it to Applications.');
278
+ log(' 2. First launch is Gatekeeper-blocked (it is open-source / unsigned):');
279
+ log(' xattr -dr com.apple.quarantine /Applications/VOICEVOX.app && open -a VOICEVOX');
280
+ log(' 3. Then run: ai-notify voicevox setup (or ai-notify voicevox on)');
281
+ return;
282
+ }
256
283
  if (sub === 'speakers') {
257
284
  const list = voicevox.listSpeakers(url);
258
285
  if (!list.length) return log(`No speakers (is VOICEVOX running at ${url}?).`);
@@ -524,7 +551,7 @@ Usage:
524
551
  ai-notify toggle | on | off | status control the mute switch
525
552
  ai-notify volume [0.0-2.0] get/set output volume
526
553
  ai-notify voice [number|name|preview|default] pick the spoken voice
527
- ai-notify voicevox [on <id>|off|speakers|test] speak in VOICEVOX character voices
554
+ ai-notify voicevox [setup|on <id>|off|speakers|test] speak in VOICEVOX character voices
528
555
  ai-notify menubar [install|uninstall|status] native menu bar bell (macOS)
529
556
  ai-notify translate [on <lang>|off|test] speak agent text in your language
530
557
  ai-notify doctor check deps & wiring
package/src/voicevox.mjs CHANGED
@@ -11,13 +11,55 @@
11
11
  import { execSync, execFileSync } from 'node:child_process';
12
12
  import { existsSync, statSync, mkdtempSync, rmSync, appendFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
- import { tmpdir } from 'node:os';
14
+ import { tmpdir, homedir } from 'node:os';
15
15
  import { stateDir } from './state.mjs';
16
16
 
17
17
  export const DEFAULT_URL = 'http://127.0.0.1:50021';
18
+ export const DOWNLOAD_URL = 'https://voicevox.hiroshiba.jp/';
18
19
 
19
20
  const platform = process.platform;
20
21
 
22
+ const sleep = (ms) => {
23
+ try {
24
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
25
+ } catch {
26
+ /* SharedArrayBuffer unavailable — skip the wait */
27
+ }
28
+ };
29
+
30
+ // Is the VOICEVOX app installed (macOS)?
31
+ export const appInstalled = () => {
32
+ if (platform !== 'darwin') return false;
33
+ return ['/Applications/VOICEVOX.app', join(homedir(), 'Applications/VOICEVOX.app')].some((p) => existsSync(p));
34
+ };
35
+
36
+ export const launchApp = () => {
37
+ try {
38
+ if (platform === 'darwin') execFileSync('open', ['-a', 'VOICEVOX']);
39
+ } catch {
40
+ /* ignore */
41
+ }
42
+ };
43
+
44
+ export const openDownloadPage = () => {
45
+ try {
46
+ if (platform === 'darwin') execFileSync('open', [DOWNLOAD_URL]);
47
+ else if (platform === 'linux') execFileSync('xdg-open', [DOWNLOAD_URL]);
48
+ } catch {
49
+ /* ignore */
50
+ }
51
+ };
52
+
53
+ // Poll until the engine answers, or timeout.
54
+ export const waitForEngine = (url = DEFAULT_URL, timeoutMs = 40000) => {
55
+ const start = Date.now();
56
+ while (Date.now() - start < timeoutMs) {
57
+ if (isAvailable(url, 1500)) return true;
58
+ sleep(2000);
59
+ }
60
+ return false;
61
+ };
62
+
21
63
  // Record why a synthesis fell back to the OS voice, so intermittent fallbacks
22
64
  // are diagnosable instead of silent. Best-effort.
23
65
  const logFail = (reason) => {