Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,19 @@ jobs:
run: |
shellcheck --severity=error \
scripts/cheer.sh \
scripts/lib/*.sh \
scripts/animations/*.sh \
scripts/voices/*.sh \
bin/cheer \
tests/*.sh \
scripts/check-secrets.sh \
scripts/install-hooks.sh

- name: Scan for secrets
run: bash scripts/check-secrets.sh

- name: Run smoke test
run: |
CHEERER_ENABLED=false bash scripts/cheer.sh
CHEERER_LANG=en CHEERER_DUMB=true bash scripts/cheer.sh
CHEERER_LANG=ja CHEERER_DUMB=true bash scripts/cheer.sh
- name: Run test suite
run: bash tests/run.sh all

release:
if: startsWith(github.ref, 'refs/tags/v')
Expand Down
46 changes: 14 additions & 32 deletions bin/cheer
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"

. "$SCRIPT_DIR/scripts/lib/config.sh"

if [[ "${1:-}" == "--epic" ]]; then
export CHEERER_ANIM=epic
shift
Expand All @@ -12,16 +14,14 @@ if [[ "${1:-}" == "--version" ]]; then
fi

if [[ "${1:-}" == "--disable" ]]; then
CHEERER_DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.config/cheerer}"
mkdir -p "$CHEERER_DATA_DIR"
printf 'CHEERER_ENABLED=false\n' > "$CHEERER_DATA_DIR/config.sh"
config_ensure_data_dir
printf 'CHEERER_ENABLED=false\n' > "$(config_file_path)"
echo "cheerer disabled. Run 'cheer --enable' to re-enable."
exit 0
fi

if [[ "${1:-}" == "--enable" ]]; then
CHEERER_DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.config/cheerer}"
rm -f "$CHEERER_DATA_DIR/config.sh"
rm -f "$(config_file_path)"
echo "cheerer enabled."
exit 0
fi
Expand Down Expand Up @@ -66,39 +66,21 @@ if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
fi

_cheerer_config() {
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_STYLE="${CHEERER_STYLE:-${CLAUDE_PLUGIN_OPTION_STYLE:-adaptive}}"
CHEERER_INTENSITY="${CHEERER_INTENSITY:-${CLAUDE_PLUGIN_OPTION_INTENSITY:-normal}}"
CHEERER_MODE="${CHEERER_MODE:-auto}"
CHEERER_COOLDOWN="${CHEERER_COOLDOWN:-3}"
CHEERER_ANIM_DURATION="${CHEERER_ANIM_DURATION:-30}"
CHEERER_EPIC="${CHEERER_EPIC:-false}"
CHEERER_EPIC_THRESHOLD="${CHEERER_EPIC_THRESHOLD:-60}"
config_load_file "$(config_file_path)"
config_apply_defaults

echo ""
echo " cheerer — Current Configuration"
echo ""
printf " CHEERER_ENABLED=%s\n" "$CHEERER_ENABLED"
printf " CHEERER_LANG=%s\n" "$CHEERER_LANG"
printf " CHEERER_ANIM=%s\n" "$CHEERER_ANIM"
printf " CHEERER_VOICE=%s\n" "$CHEERER_VOICE"
printf " CHEERER_STYLE=%s\n" "$CHEERER_STYLE"
printf " CHEERER_INTENSITY=%s\n" "$CHEERER_INTENSITY"
printf " CHEERER_MODE=%s\n" "$CHEERER_MODE"
printf " CHEERER_COOLDOWN=%s\n" "$CHEERER_COOLDOWN"
printf " CHEERER_ANIM_DURATION=%s\n" "$CHEERER_ANIM_DURATION"
printf " CHEERER_EPIC=%s\n" "$CHEERER_EPIC"
printf " CHEERER_EPIC_THRESHOLD=%s\n" "$CHEERER_EPIC_THRESHOLD"
config_print_current
echo ""

local config_file="${CLAUDE_PLUGIN_DATA:-$HOME/.config/cheerer}/config.sh"
if [[ -f "$config_file" ]]; then
printf " Config file: %s (active)\n" "$config_file"
local _cfg_file
_cfg_file="$(config_file_path)"
if [[ -f "$_cfg_file" ]]; then
printf " Config file: %s (active)\n" "$_cfg_file"
else
printf " Config file: %s (not found)\n" "$config_file"
printf " Config file: %s (not found)\n" "$_cfg_file"
fi
echo ""
exit 0
Expand All @@ -109,7 +91,7 @@ if [[ "${1:-}" == "--config" ]]; then
fi

_cheerer_stats() {
CHEERER_DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.config/cheerer}"
config_ensure_data_dir
STATS_FILE="$CHEERER_DATA_DIR/stats.json"
HISTORY_FILE="$CHEERER_DATA_DIR/history.log"

Expand Down
7 changes: 6 additions & 1 deletion scripts/animations/basketball.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[38;5;208m' $'\033[33m' $'\033[1;32m' $'\033[38;5;208
DANMAKU_SPEED=(3 4 2 3 5 4 )
DANMAKU_DELAY=(0 5 2 8 12 3 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
7 changes: 6 additions & 1 deletion scripts/animations/dance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[96m' $'\033[33m' $'\033[1;32m' $'\033[35m'
DANMAKU_SPEED=(3 2 2 4 3 5 )
DANMAKU_DELAY=(0 5 2 10 3 15 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
7 changes: 6 additions & 1 deletion scripts/animations/fireworks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[33m' $'\033[31m' $'\033[1;33m' $'\033[35m'
DANMAKU_SPEED=(3 4 2 3 2 5 )
DANMAKU_DELAY=(0 5 2 8 12 3 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
7 changes: 6 additions & 1 deletion scripts/animations/rocket.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[31m' $'\033[38;5;208m' $'\033[1;96m' $'\033[33
DANMAKU_SPEED=(2 4 2 3 3 5 )
DANMAKU_DELAY=(0 5 2 8 12 3 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
7 changes: 6 additions & 1 deletion scripts/animations/trophy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[33m' $'\033[97m' $'\033[1;33m' $'\033[35m'
DANMAKU_SPEED=(3 2 2 3 4 3 )
DANMAKU_DELAY=(0 5 2 8 12 3 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
7 changes: 6 additions & 1 deletion scripts/animations/wave.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

ANIM_LIB="$(dirname "${BASH_SOURCE[0]}")/../lib/animation.sh"
if [[ ! -f "$ANIM_LIB" ]]; then
printf '🎉 %s\n' "${CHEERER_MESSAGE:-Great work!}"
printf '🎉 %s\n' "$(printf '%s' "${CHEERER_MESSAGE:-Great work!}" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037')"
exit 0
fi
. "$ANIM_LIB"
Expand All @@ -20,4 +20,9 @@ DANMAKU_COLOR=($'\033[34m' $'\033[96m' $'\033[1;32m' $'\033[34m'
DANMAKU_SPEED=(3 4 2 3 5 3 )
DANMAKU_DELAY=(0 5 2 8 12 3 )

if ! anim_validate_theme; then
anim_fallback_plain
exit 0
fi

anim_danmaku_run
61 changes: 18 additions & 43 deletions scripts/cheer.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
#!/bin/bash
set +e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

. "$SCRIPT_DIR/lib/config.sh"

_cheer_load_config() {
CHEERER_DATA_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.config/cheerer}"
if [[ -f "$CHEERER_DATA_DIR/config.sh" ]]; then
if grep -qE '^[[:space:]]*CHEERER_[A-Z_]+=' "$CHEERER_DATA_DIR/config.sh" 2>/dev/null; then
if ! grep -qvE '^[[:space:]]*(CHEERER_[A-Z_]+=.*|#.*|)[[:space:]]*$' "$CHEERER_DATA_DIR/config.sh" 2>/dev/null; then
. "$CHEERER_DATA_DIR/config.sh"
fi
fi
fi
config_ensure_data_dir
config_load_file "$(config_file_path)"
}

_cheer_check_enabled() {
Expand All @@ -25,15 +23,15 @@ _cheer_setup_tty() {
fi
}

_cheer_parse_hook_event() {
_cheer_read_hook_payload() {
HOOK_PAYLOAD=""
local _raw
if read -r -t 1 _raw 2>/dev/null; then :; fi
HOOK_EVENT=$(printf '%s' "$_raw" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)
TASK_DURATION=$(printf '%s' "$_raw" | grep -o '"duration_seconds"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*$')
if read -r -t 1 _raw 2>/dev/null; then
HOOK_PAYLOAD="$_raw"
fi
}

_cheer_setup_dirs() {
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHEERER_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ANIM_DIR="$SCRIPT_DIR/animations"
VOICE_DIR="$SCRIPT_DIR/voices"
Expand All @@ -42,27 +40,8 @@ _cheer_setup_dirs() {
}

_cheer_validate_config() {
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_STYLE="${CHEERER_STYLE:-${CLAUDE_PLUGIN_OPTION_STYLE:-adaptive}}"
CHEERER_INTENSITY="${CHEERER_INTENSITY:-${CLAUDE_PLUGIN_OPTION_INTENSITY:-normal}}"
CHEERER_DUMB="${CHEERER_DUMB:-auto}"
CHEERER_MODE="${CHEERER_MODE:-auto}"
CHEERER_COOLDOWN="${CHEERER_COOLDOWN:-3}"
CHEERER_EPIC_THRESHOLD="${CHEERER_EPIC_THRESHOLD:-60}"
CHEERER_EPIC="${CHEERER_EPIC:-false}"

case "$CHEERER_LANG" in zh|en|ja|ko|es) ;; *) CHEERER_LANG="zh" ;; esac
case "$CHEERER_STYLE" in adaptive|balanced|hype|cozy) ;; *) CHEERER_STYLE="adaptive" ;; esac
case "$CHEERER_INTENSITY" in soft|normal|high) ;; *) CHEERER_INTENSITY="normal" ;; esac
case "$CHEERER_MODE" in auto|full|text) ;; *) CHEERER_MODE="auto" ;; esac
case "$CHEERER_DUMB" in auto|true|false) ;; *) CHEERER_DUMB="auto" ;; esac

if [[ "$CHEERER_DUMB" == "auto" ]]; then
CHEERER_DUMB=false
[[ "${TERM:-}" == "dumb" ]] || [[ -z "${TERM:-}" ]] && CHEERER_DUMB=true
fi
config_apply_defaults
config_resolve_runtime_flags
}

_cheer_check_cooldown() {
Expand Down Expand Up @@ -109,25 +88,21 @@ _cheer_apply_anim_override() {
_cheer_load_config
_cheer_check_enabled
_cheer_setup_tty
_cheer_parse_hook_event
_cheer_read_hook_payload
_cheer_setup_dirs
_cheer_validate_config

. "$SCRIPT_DIR/lib/state.sh"
. "$SCRIPT_DIR/lib/context.sh"
. "$SCRIPT_DIR/lib/policy.sh"
. "$SCRIPT_DIR/lib/render.sh"

state_init
CHEERER_FIRST_RUN="false"
[[ "${STATS_TOTAL_TRIGGERS:-0}" -eq 0 ]] && CHEERER_FIRST_RUN="true"
CURRENT_TS=$(date +%s 2>/dev/null || echo 0)
CURRENT_ISO=$(date -Iseconds 2>/dev/null || date)
export CHEERER_HOUR="${CHEERER_HOUR:-$(date +%H 2>/dev/null || echo 12)}"
export CHEERER_ANIM_DURATION="${CHEERER_ANIM_DURATION:-}"
RECENT_TASKCOMPLETED_COUNT=$(state_recent_count $((CURRENT_TS - 300)) "TaskCompleted")
SESSION_STREAK=$(state_recent_count $((CURRENT_TS - 1800)) "TaskCompleted")
RECENT_ANIMATIONS="$(state_recent_values_csv 6 3)"
RECENT_MESSAGE_IDS="$(state_recent_values_csv 7 3)"
export CHEERER_ANIM_DURATION
context_build_runtime "$HOOK_PAYLOAD"
context_publish

_cheer_check_cooldown
_cheer_check_epic
Expand Down
26 changes: 26 additions & 0 deletions scripts/lib/animation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ anim_sanitize_msg() {
printf '%s' "$raw" | sed $'s/\033\[[0-9;]*[A-Za-z]//g' | tr -d '\001-\037'
}

anim_validate_theme() {
local expected="${DANMAKU_ROWS:-0}"
local i row

[[ "$expected" =~ ^[0-9]+$ ]] || return 1
[[ "$expected" -ge 1 ]] || return 1
[[ "${#DANMAKU_ROW[@]}" -eq "$expected" ]] || return 1
[[ "${#DANMAKU_TEXT[@]}" -eq "$expected" ]] || return 1
[[ "${#DANMAKU_COLOR[@]}" -eq "$expected" ]] || return 1
[[ "${#DANMAKU_SPEED[@]}" -eq "$expected" ]] || return 1
[[ "${#DANMAKU_DELAY[@]}" -eq "$expected" ]] || return 1

for ((i=0; i<expected; i++)); do
row="${DANMAKU_ROW[$i]}"
[[ "$row" =~ ^[0-9]+$ ]] || return 1
[[ "$row" -ge 1 ]] || return 1
[[ "$row" -le "$expected" ]] || return 1
done
}

anim_fallback_plain() {
local plain="${CHEERER_MESSAGE:-Great work!}"
plain="$(anim_sanitize_msg "$plain")"
printf '🎉 %s\n' "$plain"
}

# Draw one frame of the danmaku animation.
# Reads DANMAKU_* arrays and _ANIM_FRAME counter.
# NOTE: only one stream is rendered per row per frame (rightmost visible wins).
Expand Down
Loading
Loading