diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1792306..bbbe8b7 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -26,7 +26,7 @@ "voice": { "type": "string", "title": "Voice output", - "description": "Enable voice output (on / off / true / false, default: on)", + "description": "Enable voice output (on / off / true / false, default: off)", "sensitive": false }, "style": { diff --git a/README.en.md b/README.en.md index 33209dc..1f3f0ce 100644 --- a/README.en.md +++ b/README.en.md @@ -4,7 +4,7 @@ ![Shell](https://img.shields.io/badge/Shell-Bash-89e051?logo=gnu-bash&logoColor=white) ![GitHub Repo stars](https://img.shields.io/github/stars/chinadbo/cheerer?style=social) -Whenever Claude Code finishes a task, cheerer plays a danmaku (bullet-screen) floating-subtitle animation and a multilingual voice encouragement to make coding more fun. +Whenever Claude Code finishes a task, cheerer plays a danmaku (bullet-screen) floating-subtitle animation and a contextual encouragement message to make coding more fun. Voice output is available and can be enabled when you want spoken encouragement. ## ✨ Features @@ -160,7 +160,7 @@ bash scripts/voices/cheer_ja.sh | `CHEERER_ENABLED` | Master switch | `true` / `false` | `true` | | `CHEERER_LANG` | Voice language | `zh` / `en` / `ja` / `ko` / `es` | `zh` | | `CHEERER_ANIM` | Animation style | `basketball` / `dance` / `fireworks` / `rocket` / `trophy` / `wave` / `epic` / `random` | `random` | -| `CHEERER_VOICE` | Enable or disable voice | `on` / `off` / `true` / `false` | `on` | +| `CHEERER_VOICE` | Enable or disable voice | `on` / `off` / `true` / `false` | `off` | | `CHEERER_DUMB` | Force text-only fallback or keep auto-detect | `auto` / `true` / `false` | `auto` | | `CHEERER_MODE` | Output mode | `auto` / `full` / `text` | `auto` | | `CHEERER_COOLDOWN` | Cooldown seconds between triggers | positive integer | `3` | @@ -172,6 +172,8 @@ bash scripts/voices/cheer_ja.sh `CHEERER_*` env vars override plugin settings. +Voice is opt-in on fresh installs. Leave CHEERER_VOICE unset (or set it to off) to keep cheerer text-first, and set it to on when you want spoken encouragement. + ### Runtime behavior - `CHEERER_MODE=auto` animates `TaskCompleted` hooks and keeps `Stop` hooks text-only unless `CHEERER_INTENSITY=high`. diff --git a/README.ja.md b/README.ja.md index 0580b12..29eab94 100644 --- a/README.ja.md +++ b/README.ja.md @@ -6,7 +6,7 @@ **言語:** [English](README.md) | [中文](README.zh.md) | 日本語 -Claude Code がタスクを完了すると、cheerer はターミナルで弾幕アニメーションと多言語の音声応援を再生し、コーディングをもっと楽しくします。 +Claude Code がタスクを完了すると、cheerer はターミナルで弾幕アニメーションと応援メッセージを表示し、コーディングをもっと楽しくします。音声応援が必要なときは音声出力を有効にできます。 ## ✨ 主な機能 @@ -122,13 +122,15 @@ Claude Code が `/plugin enable cheerer` 中に設定入力を表示した場合 /plugin enable cheerer > 音声言語(zh / en / ja / ko / es): ja > アニメーション(random / basketball / dance / fireworks / rocket / trophy / wave / epic): random -> 音声出力(on / off): on +> 音声出力(on / off): off > 応援スタイル(adaptive / balanced / hype / cozy): adaptive > 応援の強さ(soft / normal / high): normal ``` 入力プロンプトが表示されない場合は、同じ設定を環境変数で指定してください。 +新規インストールでは音声はオプトインです。CHEERER_VOICE を未設定のままにするか off に設定するとテキスト優先のままになり、音声応援が必要な場合だけ on にしてください。 + ### 方法2:環境変数 `~/.bashrc` / `~/.zshrc` または `.claude/settings.json` に設定: @@ -138,7 +140,7 @@ Claude Code が `/plugin enable cheerer` 中に設定入力を表示した場合 | `CHEERER_ENABLED` | マスタースイッチ | `true` / `false` | `true` | | `CHEERER_LANG` | 音声言語 | `zh` / `en` / `ja` / `ko` / `es` | `zh` | | `CHEERER_ANIM` | アニメーション | `basketball` / `dance` / `fireworks` / `rocket` / `trophy` / `wave` / `epic` / `random` | `random` | -| `CHEERER_VOICE` | 音声出力 | `on` / `off` / `true` / `false` | `on` | +| `CHEERER_VOICE` | 音声出力 | `on` / `off` / `true` / `false` | `off` | | `CHEERER_DUMB` | テキストのみを強制するか自動判定を使う | `auto` / `true` / `false` | `auto` | | `CHEERER_MODE` | 出力モード | `auto` / `full` / `text` | `auto` | | `CHEERER_COOLDOWN` | トリガー間クールダウン(秒) | 正の整数 | `3` | diff --git a/README.md b/README.md index faf20fa..e049bdf 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **Language:** English | [中文](README.zh.md) | [日本語](README.ja.md) -Whenever Claude Code finishes a task, cheerer plays a danmaku (bullet-screen) floating-subtitle animation and a multilingual voice encouragement to make coding more fun. +Whenever Claude Code finishes a task, cheerer plays a danmaku (bullet-screen) floating-subtitle animation and a contextual encouragement message to make coding more fun. Voice output is available and can be enabled when you want spoken encouragement. ## ✨ Features @@ -122,13 +122,15 @@ If Claude Code prompts for plugin settings during `/plugin enable cheerer`, you /plugin enable cheerer > Voice language (zh / en / ja): zh > Animation style (random / basketball / dance / fireworks / epic): random -> Enable voice output (on / off): on +> Enable voice output (on / off): off > Celebration style (adaptive / balanced / hype / cozy): adaptive > Celebration intensity (soft / normal / high): normal ``` If no prompt appears, set the same values with environment variables instead. +Voice is opt-in on fresh installs. Leave CHEERER_VOICE unset (or set it to off) to keep cheerer text-first, and set it to on when you want spoken encouragement. + ### Option 2: Environment variables Set in your shell profile (`~/.bashrc`, `~/.zshrc`) or `.claude/settings.json`: @@ -138,7 +140,7 @@ Set in your shell profile (`~/.bashrc`, `~/.zshrc`) or `.claude/settings.json`: | `CHEERER_ENABLED` | Master switch | `true` / `false` | `true` | | `CHEERER_LANG` | Voice language | `zh` / `en` / `ja` / `ko` / `es` | `zh` | | `CHEERER_ANIM` | Animation style | `basketball` / `dance` / `fireworks` / `rocket` / `trophy` / `wave` / `epic` / `random` | `random` | -| `CHEERER_VOICE` | Voice output | `on` / `off` / `true` / `false` | `on` | +| `CHEERER_VOICE` | Voice output | `on` / `off` / `true` / `false` | `off` | | `CHEERER_DUMB` | Force text-only fallback or keep auto-detect | `auto` / `true` / `false` | `auto` | | `CHEERER_MODE` | Output mode | `auto` / `full` / `text` | `auto` | | `CHEERER_COOLDOWN` | Cooldown between triggers (seconds) | positive integer | `3` | diff --git a/README.zh.md b/README.zh.md index de832b3..55a35db 100644 --- a/README.zh.md +++ b/README.zh.md @@ -6,7 +6,7 @@ **语言:** [English](README.md) | 中文 | [日本語](README.ja.md) -每当 Claude Code 完成任务时,在终端播放弹幕动画 + 多语言语音鼓励,让编码更快乐! +每当 Claude Code 完成任务时,在终端播放弹幕动画与鼓励文字,让编码更快乐!开启语音后还可享受多语言语音鼓励。 ## ✨ 功能 @@ -122,13 +122,15 @@ chmod +x ~/.cheerer/bin/cheer /plugin enable cheerer > 语音语言(zh / en / ja / ko / es):zh > 动画类型(random / basketball / dance / fireworks / rocket / trophy / wave / epic):random -> 启用语音(on / off):on +> 启用语音(on / off):off > 鼓励风格(adaptive / balanced / hype / cozy):adaptive > 鼓励强度(soft / normal / high):normal ``` 如果没有出现交互提示,也可以直接通过环境变量完成同样的配置。 +语音在全新安装时默认为可选关闭状态。保持 CHEERER_VOICE 未设置(或显式设为 off)即可维持纯文本优先,需要语音鼓励时再设为 on。 + ### 方式二:环境变量 在 `~/.bashrc` / `~/.zshrc` 或 `.claude/settings.json` 中设置: @@ -138,7 +140,7 @@ chmod +x ~/.cheerer/bin/cheer | `CHEERER_ENABLED` | 主开关 | `true` / `false` | `true` | | `CHEERER_LANG` | 语音语言 | `zh` / `en` / `ja` / `ko` / `es` | `zh` | | `CHEERER_ANIM` | 动画类型 | `basketball` / `dance` / `fireworks` / `rocket` / `trophy` / `wave` / `epic` / `random` | `random` | -| `CHEERER_VOICE` | 语音开关 | `on` / `off` / `true` / `false` | `on` | +| `CHEERER_VOICE` | 语音开关 | `on` / `off` / `true` / `false` | `off` | | `CHEERER_DUMB` | 强制纯文本降级或保持自动检测 | `auto` / `true` / `false` | `auto` | | `CHEERER_MODE` | 输出模式 | `auto` / `full` / `text` | `auto` | | `CHEERER_COOLDOWN` | 两次触发的冷却时间(秒) | 正整数 | `3` | diff --git a/bin/cheer b/bin/cheer index 59ec792..d6487bf 100755 --- a/bin/cheer +++ b/bin/cheer @@ -49,7 +49,7 @@ Environment variables: CHEERER_ENABLED Master switch (true/false, default: true) CHEERER_LANG Voice language (zh/en/ja/ko/es, default: zh) CHEERER_ANIM Animation style (random/[name]/epic, default: random) - CHEERER_VOICE Voice output (on/off, default: on) + CHEERER_VOICE Voice output (on/off, default: off) CHEERER_STYLE Celebration style (adaptive/balanced/hype/cozy) CHEERER_INTENSITY Intensity (soft/normal/high) CHEERER_MODE Output mode (auto/full/text) diff --git a/scripts/lib/config.sh b/scripts/lib/config.sh index 0de73c1..ec87650 100644 --- a/scripts/lib/config.sh +++ b/scripts/lib/config.sh @@ -70,7 +70,7 @@ config_apply_defaults() { CHEERER_ENABLED="${CHEERER_ENABLED:-true}" CHEERER_LANG="${CHEERER_LANG:-${CLAUDE_PLUGIN_OPTION_LANG:-zh}}" CHEERER_ANIM="${CHEERER_ANIM:-${CLAUDE_PLUGIN_OPTION_ANIM:-random}}" - CHEERER_VOICE="${CHEERER_VOICE:-${CLAUDE_PLUGIN_OPTION_VOICE:-on}}" + CHEERER_VOICE="${CHEERER_VOICE:-${CLAUDE_PLUGIN_OPTION_VOICE:-off}}" CHEERER_STYLE="${CHEERER_STYLE:-${CLAUDE_PLUGIN_OPTION_STYLE:-adaptive}}" CHEERER_INTENSITY="${CHEERER_INTENSITY:-${CLAUDE_PLUGIN_OPTION_INTENSITY:-normal}}" CHEERER_DUMB="${CHEERER_DUMB:-auto}" diff --git a/scripts/lib/render.sh b/scripts/lib/render.sh index aceeb38..5ad3463 100644 --- a/scripts/lib/render.sh +++ b/scripts/lib/render.sh @@ -155,7 +155,7 @@ render_emit() { fi export CHEERER_DUMB="${CHEERER_DUMB:-false}" - export CHEERER_VOICE="${CHEERER_VOICE:-on}" + export CHEERER_VOICE="${CHEERER_VOICE:-off}" if [[ -f "$voice_script" ]]; then bash "$voice_script" diff --git a/scripts/voices/cheer.sh b/scripts/voices/cheer.sh index f215351..def66de 100755 --- a/scripts/voices/cheer.sh +++ b/scripts/voices/cheer.sh @@ -24,7 +24,7 @@ else printf '\033[1;32m🎉 %s\033[0m\n' "$_msg" fi -CHEERER_VOICE="${CHEERER_VOICE:-on}" +CHEERER_VOICE="${CHEERER_VOICE:-off}" if [[ "$CHEERER_VOICE" == "off" ]] || [[ "$CHEERER_VOICE" == "false" ]]; then exit 0 fi diff --git a/tests/config_test.sh b/tests/config_test.sh index fd0f768..73ef09a 100644 --- a/tests/config_test.sh +++ b/tests/config_test.sh @@ -5,6 +5,20 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" . "$ROOT_DIR/tests/test_lib.sh" . "$ROOT_DIR/scripts/lib/config.sh" +# --------------------------------------------------------------------------- +# test_config_apply_defaults_uses_voice_off_when_unset +# --------------------------------------------------------------------------- +test_config_apply_defaults_uses_voice_off_when_unset() { + unset CHEERER_LANG CHEERER_ANIM CHEERER_VOICE CHEERER_STYLE CHEERER_INTENSITY \ + CHEERER_DUMB CHEERER_MODE CHEERER_COOLDOWN CHEERER_ANIM_DURATION \ + CHEERER_EPIC CHEERER_EPIC_THRESHOLD CHEERER_ENABLED 2>/dev/null || true + unset CLAUDE_PLUGIN_OPTION_VOICE 2>/dev/null || true + + config_apply_defaults + + assert_eq "off" "$CHEERER_VOICE" || return 1 +} + # --------------------------------------------------------------------------- # test_config_apply_defaults_uses_plugin_options # --------------------------------------------------------------------------- @@ -187,6 +201,7 @@ test_config_print_current_lists_effective_values() { # --------------------------------------------------------------------------- # Run # --------------------------------------------------------------------------- +run_test "config_apply_defaults_uses_voice_off_when_unset" test_config_apply_defaults_uses_voice_off_when_unset run_test "config_apply_defaults_uses_plugin_options" test_config_apply_defaults_uses_plugin_options run_test "config_apply_defaults_normalizes_invalid_values" test_config_apply_defaults_normalizes_invalid_values run_test "config_load_file_rejects_non_cheerer_lines" test_config_load_file_rejects_non_cheerer_lines diff --git a/tests/integration_test.sh b/tests/integration_test.sh index 1f196ab..e4e3987 100644 --- a/tests/integration_test.sh +++ b/tests/integration_test.sh @@ -530,6 +530,7 @@ test_help_flag_shows_usage() { assert_contains "$output" "CHEERER_LANG" assert_contains "$output" "CHEERER_COOLDOWN" assert_contains "$output" "CHEERER_ANIM_DURATION" + assert_contains "$output" "CHEERER_VOICE Voice output (on/off, default: off)" } test_config_flag_shows_values() { @@ -545,6 +546,7 @@ test_config_flag_shows_defaults() { local output output="$(bash bin/cheer --config 2>&1)" assert_contains "$output" "CHEERER_LANG=zh" + assert_contains "$output" "CHEERER_VOICE=off" assert_contains "$output" "CHEERER_COOLDOWN=3" assert_contains "$output" "CHEERER_ANIM_DURATION=30" } @@ -730,4 +732,49 @@ run_test "doctor_cli_reports_missing_optional_file_as_warn" test_doctor_cli_repo run_test "doctor_cli_exits_non_zero_for_invalid_animation" test_doctor_cli_exits_non_zero_for_invalid_animation run_test "why_cli_explains_short_task_fixture" test_why_cli_explains_short_task_fixture run_test "why_cli_uses_installed_plugin_root_resolution" test_why_cli_uses_installed_plugin_root_resolution + +test_shared_voice_script_defaults_off_when_unset() { + local tmp_dir bin_dir output + tmp_dir="$(make_tmp_dir)" + bin_dir="$tmp_dir/bin" + mkdir -p "$bin_dir" + + printf '#!/bin/bash\nprintf "say-called\\n" >> "%s"\n' "$tmp_dir/voice.log" > "$bin_dir/say" + chmod +x "$bin_dir/say" + + output="$(PATH="$bin_dir:$PATH" CHEERER_LANG="en" CHEERER_DUMB="true" bash scripts/voices/cheer.sh)" + + assert_contains "$output" "🎉 Great work. Task complete." + [[ ! -f "$tmp_dir/voice.log" ]] || return 1 +} + +test_shared_voice_script_respects_explicit_on() { + local tmp_dir bin_dir i + tmp_dir="$(make_tmp_dir)" + bin_dir="$tmp_dir/bin" + mkdir -p "$bin_dir" + + printf '#!/bin/bash\nsleep 0.6\nprintf "say-called\\n" > "%s"\n' "$tmp_dir/voice.log" > "$bin_dir/say" + chmod +x "$bin_dir/say" + + PATH="$bin_dir:$PATH" CHEERER_LANG="en" CHEERER_DUMB="true" CHEERER_VOICE="on" bash scripts/voices/cheer.sh >/dev/null + + for i in 1 2 3 4 5 6 7 8 9 10; do + [[ -f "$tmp_dir/voice.log" ]] && break + sleep 0.2 + done + + [[ -f "$tmp_dir/voice.log" ]] || return 1 +} + +run_test "shared_voice_script_defaults_off_when_unset" test_shared_voice_script_defaults_off_when_unset +run_test "shared_voice_script_respects_explicit_on" test_shared_voice_script_respects_explicit_on + +test_plugin_manifest_reports_voice_default_off() { + local manifest + manifest="$(< .claude-plugin/plugin.json)" + assert_contains "$manifest" "Enable voice output (on / off / true / false, default: off)" +} + +run_test "plugin_manifest_reports_voice_default_off" test_plugin_manifest_reports_voice_default_off finish_tests diff --git a/tests/render_test.sh b/tests/render_test.sh index fd68a94..db0a703 100644 --- a/tests/render_test.sh +++ b/tests/render_test.sh @@ -330,4 +330,32 @@ test_render_custom_message_skips_tab_only_lines() { } run_test "render_custom_message_skips_tab_only_lines" test_render_custom_message_skips_tab_only_lines + +test_render_emit_exports_voice_off_by_default() { + local tmp_dir result + tmp_dir="$(make_tmp_dir)" + mkdir -p "$tmp_dir/voices" "$tmp_dir/animations" + + cat > "$tmp_dir/voices/cheer_en.sh" <<'VOICE_EOF' +#!/bin/bash +printf 'voice=%s\n' "${CHEERER_VOICE:-unset}" +VOICE_EOF + chmod +x "$tmp_dir/voices/cheer_en.sh" + + VOICE_DIR="$tmp_dir/voices" + ANIM_DIR="$tmp_dir/animations" + CHEERER_LANG="en" + unset CHEERER_VOICE 2>/dev/null || true + CHEERER_DUMB="true" + RENDER_ANIMATE="false" + IN_COOLDOWN="false" + RENDER_MESSAGE_TEXT="Text path still runs" + RENDER_MESSAGE_ID="voice_default_off" + + result="$(render_emit)" + + assert_contains "$result" "voice=off" +} + +run_test "render_emit_exports_voice_off_by_default" test_render_emit_exports_voice_off_by_default finish_tests