Skip to content

Claude Code 如何在完成任务后通知给主人? #4

Description

@nicejade
  • ~/.claude/settings.json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
    "ANTHROPIC_API_KEY": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "API_TIMEOUT_MS": "3000000",
    "BASH_DEFAULT_TIMEOUT_MS": "300000",
    "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "80",
    "CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
    "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "120000",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
    "CLAUDE_CODE_NO_FLICKER": "true",
    "DISABLE_ERROR_REPORTING": "1",
    "DISABLE_TELEMETRY": "1",
    "ENABLE_PROMPT_CACHING_1H": "1",
     "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-pro[1m]",
    "skipDangerousModePermissionPrompt": "true"
  },
  "model": "opus",
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/notify.sh '⏳ 需要你的输入' && afplay /System/Library/Sounds/Hero.aiff"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/notify.sh '✅ 任务完成' && afplay /System/Library/Sounds/Glass.aiff"
          }
        ]
      }
    ]
  },
  "skipDangerousModePermissionPrompt": true
}
  • ~/.claude/notify.sh
#!/bin/bash
# ~/.claude/notify.sh
# 用法: notify.sh <消息> <事件类型: stop|waiting>
 
message="$1"
event_type="${2:-stop}"

activate_app() {
    local app_name="$1"

    if [ -n "$app_name" ]; then
        osascript -e "tell application \"$app_name\" to activate" >/dev/null 2>&1
    fi
}

focus_apple_terminal_tty() {
    local target_tty="${TTY:-$(tty 2>/dev/null)}"

    [ -n "$target_tty" ] || return 1

    TARGET_TTY="$target_tty" osascript >/dev/null 2>&1 <<'APPLESCRIPT'
set targetTty to system attribute "TARGET_TTY"

tell application "Terminal"
    repeat with terminalWindow in windows
        repeat with terminalTab in tabs of terminalWindow
            if tty of terminalTab is targetTty then
                set selected tab of terminalWindow to terminalTab
                set index of terminalWindow to 1
                activate
                return
            end if
        end repeat
    end repeat
end tell

error "No matching Terminal tab"
APPLESCRIPT
}

focus_iterm_tty() {
    local target_tty="${TTY:-$(tty 2>/dev/null)}"

    [ -n "$target_tty" ] || return 1

    TARGET_TTY="$target_tty" osascript >/dev/null 2>&1 <<'APPLESCRIPT'
set targetTty to system attribute "TARGET_TTY"

tell application "iTerm2"
    repeat with terminalWindow in windows
        repeat with terminalTab in tabs of terminalWindow
            repeat with terminalSession in sessions of terminalTab
                if tty of terminalSession is targetTty then
                    select terminalSession
                    select terminalTab
                    set index of terminalWindow to 1
                    activate
                    return
                end if
            end repeat
        end repeat
    end repeat
end tell

error "No matching iTerm session"
APPLESCRIPT
}

return_to_origin_app() {
    # Claude Code hooks inherit the terminal environment, so TERM_PROGRAM is the
    # best lightweight signal for returning to the app that started the task.
    case "$TERM_PROGRAM" in
        Apple_Terminal)
            focus_apple_terminal_tty && return
            activate_app "Terminal" && return
            ;;
        iTerm.app)
            focus_iterm_tty && return
            activate_app "iTerm" && return
            activate_app "iTerm2" && return
            ;;
        WezTerm)
            activate_app "WezTerm" && return
            ;;
        vscode)
            activate_app "Cursor" && return
            activate_app "Visual Studio Code" && return
            ;;
    esac

    # Fallback for Cursor-launched tasks or any environment where TERM_PROGRAM
    # is missing/ambiguous.
    activate_app "Cursor" || activate_app "Terminal"
}
 
# 获取项目名
if git rev-parse --is-inside-work-tree &>/dev/null; then
    repo_name=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
else
    repo_name=$(basename "$(pwd)")
fi
 
# 后台发送通知,避免 Claude Code hook/手动命令被 afplay  say 阻塞。
(
    #  任务完成时直接回到发起任务的应用;识别不到时回到 Cursor。
    return_to_origin_app

    #  声音提示
    if [ "$event_type" = "stop" ]; then
        afplay /System/Library/Sounds/Hero.aiff
    else
        afplay /System/Library/Sounds/Glass.aiff
    fi

    #  语音播报(修正:Tingting 无连字符)
    # 可替换为 Meijia(台湾)或 Sinji(粤语)
    if [ "$event_type" = "stop" ]; then
        say -v Tingting "Claude 任务完成,请查看 $repo_name"
    else
        say -v Tingting "Claude 需要你的输入"
    fi
) >/dev/null 2>&1 &

exit 0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions