ai-notify 0.4.5 → 0.4.7

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 CHANGED
@@ -51,6 +51,7 @@ ai-notify init # インストール済みのエージェントを自動
51
51
 
52
52
  ```sh
53
53
  ai-notify init [--dry-run] [--only claude,codex] # 検出したエージェントを配線
54
+ ai-notify use <名前> [声] [音量] # このペインの名前+声+タブ名を一括設定
54
55
  ai-notify toggle | on | off | status # ミュートスイッチ
55
56
  ai-notify volume [0.0-2.0] # 音量の取得/設定
56
57
  ai-notify voice [number|name|preview|default] # 読み上げ音声を選ぶ
@@ -62,6 +63,15 @@ ai-notify doctor # 依存・配線の確認
62
63
  ai-notify uninstall # 配線をきれいに削除
63
64
  ```
64
65
 
66
+ **ペインの設定を1コマンドで。** エージェントを動かす端末の中でこれを実行すると、読み上げ名・声・音量・**ターミナルのタブ名**を一度に設定できます。メニュー操作は不要:
67
+
68
+ ```sh
69
+ ai-notify use api Kyoko # 名前「api」+声 Kyoko+タブ名→api
70
+ ai-notify use web Eddy 0.8 # +音量 0.8
71
+ ai-notify use infra vv3 # VOICEVOX スピーカー3(例: ずんだもん)
72
+ ai-notify use clear # このペインをリセット
73
+ ```
74
+
65
75
  ペイン別の上書き — エージェントを起動する**前**に、その端末で `export` する:
66
76
 
67
77
  ```sh
package/README.md CHANGED
@@ -52,6 +52,7 @@ Adding another agent (aider, opencode, amp, …) is a small PR: drop a file in `
52
52
 
53
53
  ```sh
54
54
  ai-notify init [--dry-run] [--only claude,codex] # wire detected agents
55
+ ai-notify use <name> [voice] [vol] # set THIS pane's name + voice + tab title, in one go
55
56
  ai-notify toggle | on | off | status # the mute switch
56
57
  ai-notify volume [0.0-2.0] # get/set output volume
57
58
  ai-notify voice [number|name|preview|default] # pick the spoken voice
@@ -63,6 +64,15 @@ ai-notify doctor # check deps & wiring
63
64
  ai-notify uninstall # cleanly remove wiring
64
65
  ```
65
66
 
67
+ **Set up a pane in one command.** Run this *in* the terminal where the agent runs — it names the pane (spoken in the read-out), picks its voice, sets its volume, and renames the terminal tab, all at once. No menu hopping:
68
+
69
+ ```sh
70
+ ai-notify use api Kyoko # name "api" + voice Kyoko + tab → "api"
71
+ ai-notify use web Eddy 0.8 # + volume 0.8
72
+ ai-notify use infra vv3 # VOICEVOX speaker 3 (e.g. ずんだもん)
73
+ ai-notify use clear # reset this pane
74
+ ```
75
+
66
76
  Per-window overrides — `export` these in a terminal *before* launching the agent:
67
77
 
68
78
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-notify",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
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": {
package/src/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs';
6
6
  import { execSync, execFileSync } from 'node:child_process';
7
7
  import { providers, byId } from './providers/index.mjs';
8
8
  import { emit } from './notify.mjs';
9
- import { deriveLabel, cliInvocation, isEphemeralInstall } from './util.mjs';
9
+ import { deriveLabel, cliInvocation, isEphemeralInstall, controllingTty } from './util.mjs';
10
10
  import { curatedVoices, resolveVoice, previewVoice } from './voices.mjs';
11
11
  import * as menubar from './menubar.mjs';
12
12
  import { translate } from './translate.mjs';
@@ -506,6 +506,62 @@ const cmds = {
506
506
  log(`pane ${tty}: name ${arg}`);
507
507
  },
508
508
 
509
+ // One-shot per-pane setup, run INSIDE the pane: set its spoken name, voice,
510
+ // and volume — and rename the terminal tab — in a single command, instead of
511
+ // doing each from the menu bar. Keyed by this shell's tty (which the agent's
512
+ // hook resolves to as well), so it just works for the agent running here.
513
+ // use <name> [voice] [volume] | use clear
514
+ // voice: a system voice name/number (e.g. Kyoko, 3) or VOICEVOX as vv<id> (vv3).
515
+ use() {
516
+ const [name, voiceArg, volArg] = positionals;
517
+ const tty = controllingTty();
518
+ if (!tty) {
519
+ console.error('`ai-notify use` must run inside a terminal pane (no controlling tty found).');
520
+ process.exit(1);
521
+ }
522
+ if (!name || name === 'clear' || name === 'reset') {
523
+ updatePaneSetting(tty, { speakName: null, tts: null, voice: null, speaker: null, volume: null });
524
+ process.stdout.write('\u001b]0;\u0007'); // clear the tab title (best-effort)
525
+ return log(`✓ pane reset (${tty})`);
526
+ }
527
+
528
+ const patch = { speakName: name };
529
+ let voiceLabel = '';
530
+ if (voiceArg !== undefined) {
531
+ const vv = /^(?:vv|voicevox):?(\d+)$/i.exec(voiceArg);
532
+ if (vv) {
533
+ patch.tts = 'voicevox';
534
+ patch.speaker = Number(vv[1]);
535
+ patch.voice = null;
536
+ voiceLabel = `VOICEVOX ${vv[1]}`;
537
+ } else {
538
+ const picked = resolveVoice(voiceArg, curatedVoices(10));
539
+ if (!picked) {
540
+ console.error(`unknown voice: ${voiceArg} (names/numbers: ai-notify voice; VOICEVOX: vv<id>)`);
541
+ process.exit(1);
542
+ }
543
+ patch.tts = 'say';
544
+ patch.voice = picked;
545
+ patch.speaker = null;
546
+ voiceLabel = picked;
547
+ }
548
+ }
549
+ if (volArg !== undefined) {
550
+ const v = Number(volArg);
551
+ if (Number.isFinite(v)) patch.volume = Math.min(2, Math.max(0, v));
552
+ }
553
+
554
+ updatePaneSetting(tty, patch);
555
+ // Rename this terminal tab/window to the pane name (best-effort — a shell
556
+ // that rewrites the title on each prompt may override it after you return).
557
+ process.stdout.write(`\u001b]0;${name}\u0007`);
558
+
559
+ const bits = [`name ${name}`];
560
+ if (voiceLabel) bits.push(`voice ${voiceLabel}`);
561
+ if (patch.volume !== undefined) bits.push(`volume ${patch.volume}`);
562
+ log(`✓ ${bits.join(' · ')} · tab renamed`);
563
+ },
564
+
509
565
  // Get/set the VOICEVOX base prosody (the normal-tone scales the menu bar
510
566
  // sliders drive). With no args, prints the current values as JSON.
511
567
  // voice-prosody [speed|pitch|intonation <value> | reset]
@@ -710,6 +766,7 @@ function printHelp() {
710
766
  Usage:
711
767
  ai-notify init [--dry-run] [--only claude,codex] wire detected agents
712
768
  ai-notify uninstall [--only ...] remove wiring
769
+ ai-notify use <name> [voice] [vol] name THIS pane + set its voice + rename the tab, at once
713
770
  ai-notify toggle | on | off | status control the mute switch
714
771
  ai-notify volume [0.0-2.0] get/set output volume
715
772
  ai-notify voice [number|name|preview|default] pick the spoken voice
package/src/notify.mjs CHANGED
@@ -193,7 +193,13 @@ export const emit = ({ provider = 'default', event = 'done', label = '', message
193
193
  // 3. the auto-derived label — only when speakLabel is on (else slow filler).
194
194
  const envName = (process.env.AI_NOTIFY_LABEL || '').trim();
195
195
  const spokenName = envName || pane.speakName || (config.speakLabel === true && label ? label : '');
196
- const speakText = spokenName ? `${spokenName}、${spokenBody}` : spokenBody;
196
+ // Join the pane name to the read-out as the SUBJECT. Japanese needs the は
197
+ // topic particle ("ジョンは、…") — a bare comma ("ジョン、…") reads as calling
198
+ // out TO John, not saying John is the one finishing / waiting. Other languages
199
+ // just get a comma.
200
+ const isJa = (s) => /[぀-ヿ㐀-鿿ヲ-゚]/.test(s); // kana / kanji / half-width kana
201
+ const joinName = (name, body) => (name ? `${name}${isJa(body) ? 'は、' : ', '}${body}` : body);
202
+ const speakText = joinName(spokenName, spokenBody);
197
203
 
198
204
  // Per-pane voice (precedence: $AI_NOTIFY_* env > this pane's pick > global).
199
205
  const tts = pane.tts || config.tts;
@@ -238,7 +244,7 @@ export const emit = ({ provider = 'default', event = 'done', label = '', message
238
244
  speakTone = tsundere.axisFor(eff);
239
245
  outVol = Math.min(2, Math.max(0, vol * tsundere.volumeMul(tier, ts.volumeBoost !== false)));
240
246
  outText = tsundere.wrap(spokenBody, eff, tier, ts.lang || 'ja', nextCounter('tsundere'));
241
- if (spokenName) outText = `${spokenName}、${outText}`;
247
+ if (spokenName) outText = joinName(spokenName, outText);
242
248
  if (tts === 'voicevox') {
243
249
  const sm = ts.styleMap || voicevox.resolveStyles(outSpeaker, config.voicevox?.url);
244
250
  const axis = tsundere.axisFor(eff);