/
aqa
/
claude-code
Обзор
Документация
Войти
/
aqa
/
claude-code
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
services/notifier.ts
156 строк
4 KB
kuberwastaken
new initial commit (history rewritten)
01 апр 2026, 14:57
01 апр 2026, 14:57
c1996f2
Код
Авторство
О чём код?
import type { TerminalNotification } from '../ink/useTerminalNotification.js' import { getGlobalConfig } from '../utils/config.js' import { env } from '../utils/env.js' import { execFileNoThrow } from '../utils/execFileNoThrow.js' import { executeNotificationHooks } from '../utils/hooks.js' import { logError } from '../utils/log.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent, } from './analytics/index.js' export type NotificationOptions = { message: string title?: string notificationType: string } export async function sendNotification( notif: NotificationOptions, terminal: TerminalNotification, ): Promise<void> { const config = getGlobalConfig() const channel = config.preferredNotifChannel await executeNotificationHooks(notif) const methodUsed = await sendToChannel(channel, notif, terminal) logEvent('tengu_notification_method_used', { configured_channel: channel as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, method_used: methodUsed as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, term: env.terminal as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) } const DEFAULT_TITLE = 'Claude Code' async function sendToChannel( channel: string, opts: NotificationOptions, terminal: TerminalNotification, ): Promise<string> { const title = opts.title || DEFAULT_TITLE try { switch (channel) { case 'auto': return sendAuto(opts, terminal) case 'iterm2': terminal.notifyITerm2(opts) return 'iterm2' case 'iterm2_with_bell': terminal.notifyITerm2(opts) terminal.notifyBell() return 'iterm2_with_bell' case 'kitty': terminal.notifyKitty({ ...opts, title, id: generateKittyId() }) return 'kitty' case 'ghostty': terminal.notifyGhostty({ ...opts, title }) return 'ghostty' case 'terminal_bell': terminal.notifyBell() return 'terminal_bell' case 'notifications_disabled': return 'disabled' default: return 'none' } } catch { return 'error' } } async function sendAuto( opts: NotificationOptions, terminal: TerminalNotification, ): Promise<string> { const title = opts.title || DEFAULT_TITLE switch (env.terminal) { case 'Apple_Terminal': { const bellDisabled = await isAppleTerminalBellDisabled() if (bellDisabled) { terminal.notifyBell() return 'terminal_bell' } return 'no_method_available' } case 'iTerm.app': terminal.notifyITerm2(opts) return 'iterm2' case 'kitty': terminal.notifyKitty({ ...opts, title, id: generateKittyId() }) return 'kitty' case 'ghostty': terminal.notifyGhostty({ ...opts, title }) return 'ghostty' default: return 'no_method_available' } } function generateKittyId(): number { return Math.floor(Math.random() * 10000) } async function isAppleTerminalBellDisabled(): Promise<boolean> { try { if (env.terminal !== 'Apple_Terminal') { return false } const osascriptResult = await execFileNoThrow('osascript', [ '-e', 'tell application "Terminal" to name of current settings of front window', ]) const currentProfile = osascriptResult.stdout.trim() if (!currentProfile) { return false } const defaultsOutput = await execFileNoThrow('defaults', [ 'export', 'com.apple.Terminal', '-', ]) if (defaultsOutput.code !== 0) { return false } // Lazy-load plist (~280KB with xmlbuilder+@xmldom) — only hit on // Apple_Terminal with auto-channel, which is a small fraction of users. const plist = await import('plist') const parsed: Record<string, unknown> = plist.parse(defaultsOutput.stdout) const windowSettings = parsed?.['Window Settings'] as | Record<string, unknown> | undefined const profileSettings = windowSettings?.[currentProfile] as | Record<string, unknown> | undefined if (!profileSettings) { return false } return profileSettings.Bell === false } catch (error) { logError(error) return false } }