Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:00:39 +08:00
commit 8457d2c114
12 changed files with 736 additions and 0 deletions

81
hooks/generate_message.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# メッセージ生成スクリプト
# プラグインのディレクトリを取得
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOOKS_DIR="$SCRIPT_DIR"
NOTIFY_LOG_PATH="$HOOKS_DIR/notify.log"
# 標準入力からメッセージ要約を取得
SUMMARY=$(cat)
EXISTING_EXAMPLES=$(cat <<'EOT'
- 対応を完了しました。
- 修正を反映しました。
- 最終確認をお願いします。
- 確認事項があります。
- 次の点についてご確認ください。
- ご回答をお願いします。
- 追加の情報が必要です。
- ご指示をお願いします。
- ご確認ください。
- 以上です。
EOT
)
# 直近10件のログを取得
RECENT_LOGS=""
if [ -f "$NOTIFY_LOG_PATH" ]; then
RECENT_LOGS=$(tail -n 10 "$NOTIFY_LOG_PATH" | cut -d'|' -f2)
fi
# Claude APIへのプロンプトを作成
PROMPT="あなたは無個性で中立的なアシスタントです。以下のタスク要約に対し、
感情やキャラクター性を排した、簡潔で平易な敬体のメッセージを50文字以内で生成してください。
前提: これはAgentからユーザーへの切り替え時の発言です質問がある時または対応完了時
タスク要約: $SUMMARY
【必須要件】
- 無個性・中立。感情やキャラクター性を出さない。
- です/ます調。過度に仰々しい敬語や比喩は避ける。
- 記号・絵文字・感嘆符を使わない。平易な語彙を用いる。
- 着手・開始・実行・進める等の未来時制の表現は使わない。
- 成果の報告または質問の提示に限定する。
【重複回避】
- 最近の発言(下記)と同義反復や語句の重複を避ける。
$RECENT_LOGS
【発言パターン例】
$EXISTING_EXAMPLES
【出力形式】
- 何をしたかが具体的にわかるよう、端的に記載する(前後の解説・記号・引用符なし)。"
# フォールバック文言の判定(質問/確認が含まれるかで出し分け)
if echo "$SUMMARY" | grep -Eq '[\?]|質問|確認|教えて|不明点|ご回答'; then
FALLBACK_MESSAGE="ご確認をお願いします。"
else
FALLBACK_MESSAGE="対応を完了しました。"
fi
# Claude CLIコマンドを使用してメッセージ生成
if command -v claude >/dev/null 2>&1; then
# プロンプトを直接パイプで渡す
RESPONSE=$(echo "$PROMPT" | claude -p - 2>/dev/null)
# レスポンスが取得できた場合
if [ -n "$RESPONSE" ] && [ "$RESPONSE" != "" ]; then
echo "$RESPONSE" | head -n 2
else
# Claude CLIが失敗した場合のフォールバック
echo "$FALLBACK_MESSAGE"
fi
else
# Claude CLIが利用できない場合のフォールバック
echo "$FALLBACK_MESSAGE"
fi

15
hooks/hooks.json Normal file
View File

@@ -0,0 +1,15 @@
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/notify.sh"
}
]
}
]
}
}

90
hooks/notify.sh Executable file
View File

@@ -0,0 +1,90 @@
#!/bin/bash
# 音声通知スクリプト
# 最後のメッセージを取得し、要約してメッセージを読み上げ
# 標準入力からJSONを読み取る
INPUT=$(cat)
# プラグインのディレクトリを取得
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
HOOKS_DIR="$SCRIPT_DIR"
NOTIFY_LOG_PATH="$HOOKS_DIR/notify.log"
# ログディレクトリの作成
mkdir -p "$HOOKS_DIR"
# トランスクリプトを処理(.jsonl形式に対応
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path')
if [ -f "$TRANSCRIPT_PATH" ]; then
# 最後のアシスタントメッセージのみを取得(全文)
TEMP_FILE=$(mktemp)
if command -v tail >/dev/null 2>&1; then
tail -r "$TRANSCRIPT_PATH" > "$TEMP_FILE"
LAST_MESSAGE=""
while IFS= read -r line; do
# JSON形式の妥当性をチェック
if echo "$line" | jq -e . >/dev/null 2>&1; then
if echo "$line" | jq -e '.type == "assistant"' >/dev/null 2>&1; then
LAST_MESSAGE=$(echo "$line" | jq -r '.message.content[]? | select(.type == "text") | .text' 2>/dev/null)
break
fi
fi
done < "$TEMP_FILE"
rm -f "$TEMP_FILE"
fi
# メッセージが取得できた場合の処理
if [ -n "$LAST_MESSAGE" ]; then
# 最後のメッセージの1行目を取得最大100文字
MESSAGE=$(echo "$LAST_MESSAGE" | head -n 1 | head -c 100 | sed 's/[[:space:]]*$//')
# メッセージが空の場合はデフォルト文例
if [ -z "$MESSAGE" ]; then
MESSAGE="処理が完了しました。"
fi
# プロジェクト名を取得して追加
PROJECT_PATH=$(echo "$INPUT" | jq -r '.cwd // empty')
if [ -n "$PROJECT_PATH" ]; then
PROJECT_MANAGER="$HOOKS_DIR/project_manager.sh"
if [ -f "$PROJECT_MANAGER" ]; then
PROJECT_NAME=$(bash "$PROJECT_MANAGER" get "$PROJECT_PATH" 2>/dev/null || echo "")
if [ -n "$PROJECT_NAME" ]; then
MESSAGE="${PROJECT_NAME}のたくしです。${MESSAGE}"
fi
fi
fi
# 通知実行
# terminal-notifier使用Macネイティブ通知
if command -v terminal-notifier >/dev/null 2>&1; then
terminal-notifier -message "$MESSAGE" -title "Assistant" >/dev/null 2>&1 &
fi
# 音声出力Style-Bert-VITS2
TTS_SCRIPT="$HOOKS_DIR/tts_bert_vits.sh"
if [ -f "$TTS_SCRIPT" ]; then
nohup bash "$TTS_SCRIPT" "$MESSAGE" >/dev/null 2>&1 &
fi
# ログ保存(タイムスタンプ|発言のみ、改行除去)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
CLEAN_MESSAGE=$(echo "$MESSAGE" | tr -d '\n\r' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo "$TIMESTAMP|$CLEAN_MESSAGE" >> "$NOTIFY_LOG_PATH"
# ログを10件までに制限
if [ -f "$NOTIFY_LOG_PATH" ]; then
TEMP_LOG=$(mktemp)
tail -n 10 "$NOTIFY_LOG_PATH" > "$TEMP_LOG"
mv "$TEMP_LOG" "$NOTIFY_LOG_PATH"
fi
fi
fi
echo '{"decision": "approve"}'
exit 0

126
hooks/project_manager.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/bin/bash
# Takushiプロジェクト名管理スクリプト
# 使用方法: ./project_manager.sh [action] [args...]
# action: set, get, init
CONFIG_DIR="$HOME/.config/takushi_notifier"
PROJECT_NAMES_FILE="$CONFIG_DIR/project_names.conf"
# 設定ディレクトリの作成
init_config() {
mkdir -p "$CONFIG_DIR"
if [ ! -f "$PROJECT_NAMES_FILE" ]; then
touch "$PROJECT_NAMES_FILE"
fi
}
# プロジェクト名設定
set_project_name() {
local project_path="$1"
local project_name="$2"
# 引数チェック
if [ -z "$project_path" ] || [ -z "$project_name" ]; then
echo "エラー: プロジェクトパスと名前を指定してください"
exit 1
fi
init_config
# 一時ファイルを作成
local temp_file=$(mktemp)
local var_name="PROJECT_NAME_${project_path//\//_}"
local updated=false
# 設定ファイルを読み込み、該当行を更新
if [ -f "$PROJECT_NAMES_FILE" ]; then
while IFS= read -r line; do
if [[ "$line" =~ ^${var_name}= ]]; then
echo "${var_name}=\"$project_name\"" >> "$temp_file"
updated=true
else
echo "$line" >> "$temp_file"
fi
done < "$PROJECT_NAMES_FILE"
fi
# 新規追加の場合
if [ "$updated" = false ]; then
echo "${var_name}=\"$project_name\"" >> "$temp_file"
fi
# 一時ファイルで設定ファイルを上書き
mv "$temp_file" "$PROJECT_NAMES_FILE"
echo "プロジェクト「${project_name}」を設定しました (パス: ${project_path})"
}
# プロジェクト名取得
get_project_name() {
local project_path="$1"
if [ -z "$project_path" ]; then
echo "エラー: プロジェクトパスを指定してください"
exit 1
fi
init_config
if [ -f "$PROJECT_NAMES_FILE" ]; then
local current_path="$project_path"
# 現在のパスから親階層まで順に探索
while [ -n "$current_path" ]; do
local var_name="PROJECT_NAME_${current_path//\//_}"
local project_name=$(grep "^${var_name}=" "$PROJECT_NAMES_FILE" | cut -d'=' -f2- | tr -d '"')
if [ -n "$project_name" ]; then
echo "$project_name"
return
fi
# 親ディレクトリに移動
if [ "$current_path" = "/" ] || [ "$current_path" = "." ]; then
break
fi
current_path=$(dirname "$current_path")
if [ "$current_path" = "." ]; then
break
fi
done
fi
echo ""
}
# メイン処理
case "${1:-get}" in
"set")
if [ -z "$2" ] || [ -z "$3" ]; then
echo "エラー: プロジェクトパスと名前を指定してください"
echo "使用方法: $0 set [path] [name]"
exit 1
fi
set_project_name "$2" "$3"
;;
"get")
if [ -z "$2" ]; then
echo "エラー: プロジェクトパスを指定してください"
echo "使用方法: $0 get [path]"
exit 1
fi
get_project_name "$2"
;;
"init")
init_config
echo "設定ディレクトリを初期化しました: $CONFIG_DIR"
;;
*)
echo "使用方法: $0 [set|get|init] [args...]"
echo " set [path] [name] : プロジェクト名を設定"
echo " get [path] : プロジェクト名を取得"
echo " init : 設定ディレクトリを初期化"
exit 1
;;
esac

107
hooks/tts_bert_vits.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
# Style-Bert-VITS2 音声合成スクリプト
# 使用方法: ./tts_bert_vits.sh "読み上げたいテキスト"
# API認証情報
CF_ACCESS_CLIENT_ID="78daf18f4b82f77f12a0bfec004ab4ce.access"
CF_ACCESS_CLIENT_SECRET="cded896f04ee01c47f5098cebcd3118ed09ad1bc3666f1d59cc5912b2e724020"
API_BASE_URL="https://bert-vits-web.vildas.org"
# モデル設定(固定)
MODEL_NAME="izawa_toiyomi"
MODEL_FILE="model_assets/izawa_toiyomi/izawa_toiyomi_e100_s5000.safetensors"
# 設定ファイルの読み込み
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# グローバル設定ファイルを読み込み
GLOBAL_CONFIG="$HOME/.config/takushi_notifier/volume.conf"
# グローバル設定ファイルが存在する場合は読み込み
if [ -f "$GLOBAL_CONFIG" ]; then
. "$GLOBAL_CONFIG"
fi
# 音量設定(設定ファイルで AFPLAY_VOLUME を 0.0〜1.0 で指定可能。)
AFPLAY_VOLUME="${AFPLAY_VOLUME:-1.0}"
# 引数チェック
if [ $# -eq 0 ]; then
echo "エラー: テキストを指定してください"
echo "使用方法: $0 \"読み上げたいテキスト\""
exit 1
fi
TEXT="$1"
SAVE_FILE="${2:-}" # 第2引数で出力ファイル名を指定可能省略時は一時ファイル
# 出力ファイルの設定
if [ -z "$SAVE_FILE" ]; then
# ファイル名が指定されていない場合は一時ファイルを使用macOS対応
OUTPUT_FILE=$(mktemp).wav
if [ $? -ne 0 ] || [ -z "$OUTPUT_FILE" ]; then
echo "エラー: 一時ファイルの作成に失敗しました"
exit 1
fi
TEMP_FILE=true
else
OUTPUT_FILE="$SAVE_FILE"
TEMP_FILE=false
fi
# 1. G2P処理でmoraToneListを取得
G2P_RESULT=$(curl -s -X POST "${API_BASE_URL}/api/g2p" \
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-d "{\"text\": \"${TEXT}\"}")
# エラーチェック
if [ $? -ne 0 ]; then
echo "エラー: G2P処理に失敗しました"
exit 1
fi
# 2. 音声合成
SYNTHESIS_JSON=$(cat <<EOF
{
"model": "${MODEL_NAME}",
"modelFile": "${MODEL_FILE}",
"text": "${TEXT}",
"moraToneList": ${G2P_RESULT}
}
EOF
)
# 音声合成リクエスト
HTTP_STATUS=$(curl -s -X POST "${API_BASE_URL}/api/synthesis" \
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-H "Accept: audio/wav" \
-d "${SYNTHESIS_JSON}" \
--output "${OUTPUT_FILE}" \
-w "%{http_code}")
# HTTPステータスコードチェック
if [ -n "$HTTP_STATUS" ] && [ "$HTTP_STATUS" -eq 200 ]; then
if [ -f "${OUTPUT_FILE}" ]; then
if [ "$TEMP_FILE" = true ]; then
# 一時ファイルの場合は自動再生して削除
afplay -v "${AFPLAY_VOLUME}" "${OUTPUT_FILE}"
rm "${OUTPUT_FILE}"
else
# ファイル保存の場合
echo "ファイル: ${OUTPUT_FILE}"
fi
fi
else
echo "エラー: 音声合成に失敗しました (HTTPステータス: ${HTTP_STATUS})"
if [ -f "${OUTPUT_FILE}" ]; then
echo "エラー内容:"
cat "${OUTPUT_FILE}"
rm "${OUTPUT_FILE}"
fi
exit 1
fi

85
hooks/volume_manager.sh Executable file
View File

@@ -0,0 +1,85 @@
#!/bin/bash
# Takushi音量管理スクリプト
# 使用方法: ./volume_manager.sh [action] [args...]
# action: set, get, init
CONFIG_DIR="$HOME/.config/takushi_notifier"
CONFIG_FILE="$CONFIG_DIR/volume.conf"
DEFAULT_VOLUME=50
# 設定ディレクトリの作成
init_config() {
mkdir -p "$CONFIG_DIR"
if [ ! -f "$CONFIG_FILE" ]; then
echo "AFPLAY_VOLUME=0.5" > "$CONFIG_FILE"
echo "VOLUME_PERCENT=$DEFAULT_VOLUME" >> "$CONFIG_FILE"
fi
}
# 音量設定0-100を0.0-1.0に変換)
set_volume() {
local volume_percent="$1"
# 引数チェック
if ! [[ "$volume_percent" =~ ^[0-9]+$ ]]; then
echo "エラー: 音量は0-100の整数で指定してください"
exit 1
fi
if [ "$volume_percent" -lt 0 ] || [ "$volume_percent" -gt 100 ]; then
echo "エラー: 音量は0-100の範囲で指定してください"
exit 1
fi
# 0-100を0.0-1.0に変換
local volume_float=$(echo "scale=2; $volume_percent / 100" | bc -l)
# 設定ファイルを更新
init_config
cat > "$CONFIG_FILE" << EOF
AFPLAY_VOLUME=$volume_float
VOLUME_PERCENT=$volume_percent
EOF
echo "Takushi音量を${volume_percent}%に設定しました (afplay: ${volume_float})"
}
# 現在の音量取得
get_volume() {
init_config
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
echo "現在のTakushi音量: ${VOLUME_PERCENT}% (afplay: ${AFPLAY_VOLUME})"
else
echo "設定ファイルが見つかりません。初期化します..."
init_config
echo "現在のTakushi音量: ${DEFAULT_VOLUME}% (afplay: 0.5)"
fi
}
# メイン処理
case "${1:-get}" in
"set")
if [ -z "$2" ]; then
echo "エラー: 音量を指定してください"
echo "使用方法: $0 set [0-100]"
exit 1
fi
set_volume "$2"
;;
"get")
get_volume
;;
"init")
init_config
echo "設定ディレクトリを初期化しました: $CONFIG_DIR"
;;
*)
echo "使用方法: $0 [set|get|init] [volume]"
echo " set [0-100] : 音量を設定"
echo " get : 現在の音量を表示"
echo " init : 設定ディレクトリを初期化"
exit 1
;;
esac