diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index ee1731a..8b1f030 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -1324,6 +1324,46 @@ with open(settings_path, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") PYEOF + + # Звук по завершении задачи: используем Claude Notifier из ~/.claude/hooks, + # либо минимальный fallback, если Notifier не установился. + if [ -f "$HOME/.claude/hooks/claude-notifier-on-stop.js" ]; then + stop_hook_cmd="node \"$HOME/.claude/hooks/claude-notifier-on-stop.js\"" + else + cat > "$cfg/hooks/sound-on-stop.sh" <<'SOUNDEOF' +#!/usr/bin/env bash +# Minimal task-completion sound fallback +( + paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null +) & +SOUNDEOF + chmod +x "$cfg/hooks/sound-on-stop.sh" + stop_hook_cmd="bash \"$cfg/hooks/sound-on-stop.sh\"" + fi + python3 - "$cfg/settings.json" "$stop_hook_cmd" <<'PYEOF' +import sys, json, os +settings_path, hook_cmd = sys.argv[1:3] +data = {} +if os.path.exists(settings_path): + try: + with open(settings_path) as f: + data = json.load(f) + except Exception: + pass +data.setdefault("hooks", {}).setdefault("Stop", [{"hooks": []}]) +stop_entries = data["hooks"]["Stop"] +already = any( + any(h.get("command", "") == hook_cmd for h in entry.get("hooks", [])) + for entry in stop_entries +) +if not already: + stop_entries[0]["hooks"].append({"type": "command", "command": hook_cmd}) +with open(settings_path, "w") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + f.write("\n") +PYEOF } # _apply_effort: per-launcher persistence уровня effort. @@ -1373,8 +1413,92 @@ _build_ai_sys_prompt() { done echo -e "$sp" } + +_setup_codex_sound_hooks() { + local hooks_dir="$HOME/.codex/hooks" + local hooks_json="$HOME/.codex/hooks.json" + mkdir -p "$hooks_dir" + + cat > "$hooks_dir/ai-setup-sound-on-stop.sh" <<'SOUNDEOF' +#!/usr/bin/env bash +# Minimal Codex turn-completion sound. +( + paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null +) & +SOUNDEOF + + cat > "$hooks_dir/ai-setup-sound-on-auto-compact.sh" <<'SOUNDEOF' +#!/usr/bin/env bash +# Minimal Codex context auto-compact warning sound. +( + paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null || + pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null || + aplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null +) & +SOUNDEOF + chmod +x "$hooks_dir/ai-setup-sound-on-stop.sh" "$hooks_dir/ai-setup-sound-on-auto-compact.sh" + + python3 - "$hooks_json" <<'PYEOF' +import json +import os +import sys + +path = sys.argv[1] +data = {} +if os.path.exists(path): + try: + with open(path) as f: + data = json.load(f) + except Exception: + print(f"WARNING: {path} is not valid JSON; Codex sound hooks were not installed", file=sys.stderr) + sys.exit(0) +if not isinstance(data, dict): + print(f"WARNING: {path} does not contain a JSON object; Codex sound hooks were not installed", file=sys.stderr) + sys.exit(0) + +hooks = data.setdefault("hooks", {}) +if not isinstance(hooks, dict): + print(f"WARNING: {path}.hooks is not a JSON object; Codex sound hooks were not installed", file=sys.stderr) + sys.exit(0) + +def ensure_hook(event, command, matcher=None): + groups = hooks.setdefault(event, []) + for group in groups: + if matcher is not None and group.get("matcher") != matcher: + continue + if any(h.get("command") == command for h in group.get("hooks", [])): + return + target = None + for group in groups: + if matcher is None or group.get("matcher") == matcher: + target = group + break + if target is None: + target = {"hooks": []} + if matcher is not None: + target["matcher"] = matcher + groups.append(target) + target.setdefault("hooks", []).append({ + "type": "command", + "command": command, + "timeout": 5, + }) + +ensure_hook("Stop", 'bash "$HOME/.codex/hooks/ai-setup-sound-on-stop.sh"') +ensure_hook("PreCompact", 'bash "$HOME/.codex/hooks/ai-setup-sound-on-auto-compact.sh"', "auto") + +os.makedirs(os.path.dirname(path), exist_ok=True) +with open(path, "w") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + f.write("\n") +PYEOF +} HELPEREOF chmod +x "$HELPERS_FILE" +source "$HELPERS_FILE" +_setup_codex_sound_hooks || warn "Не удалось установить Codex sound hooks" # === ai-gpt === cat > "$BIN_DIR/ai-gpt" << 'GPTEOF' @@ -1405,6 +1529,7 @@ if [ -z "$codex_bin" ] || [ ! -f "$codex_bin" ]; then fi _build_ai_sys_prompt > /dev/null # сохраняет в ~/.codex/AGENTS.md (codex читает авто) +_setup_codex_sound_hooks exec "$codex_bin" --dangerously-bypass-approvals-and-sandbox "$@" GPTEOF chmod +x "$BIN_DIR/ai-gpt" @@ -1970,7 +2095,14 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/ # agy нативно подтягивает правила и проектные .md файлы, # поэтому ручная инъекция SYS_PROMPT больше не требуется. -exec "$agy_bin" --dangerously-skip-permissions "$@" +"$agy_bin" --dangerously-skip-permissions "$@" +agy_exit=$? +( + paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || + aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null +) & +exit $agy_exit GEMINIEOF chmod +x "$BIN_DIR/ai-gemini" # Подменяем путь к agy, если BIN_DIR отличается от ~/.local/bin @@ -2002,7 +2134,7 @@ if [ "$USE_VLESS" -eq 1 ]; then sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-kimi" sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-glm" sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-openrouter" - sed -i 's/^\([[:space:]]*\)exec "\$agy_bin"/\1exec proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" "\$agy_bin"/' "$BIN_DIR/ai-gemini" + sed -i 's|^\([[:space:]]*\)"\$agy_bin" --dangerously-skip-permissions "\$@"|\1proxychains4 -f "\$HOME/.proxychains-xray.conf" "\$agy_bin" --dangerously-skip-permissions "\$@"|' "$BIN_DIR/ai-gemini" sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-claude" success "proxychains4 интегрирован" fi