forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseTerminalNotification.ts
More file actions
126 lines (115 loc) · 3.77 KB
/
Copy pathuseTerminalNotification.ts
File metadata and controls
126 lines (115 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { createContext, useCallback, useContext, useMemo } from 'react'
import { isProgressReportingAvailable, type Progress } from './terminal.js'
import { BEL } from './termio/ansi.js'
import { ITERM2, OSC, osc, PROGRESS, wrapForMultiplexer } from './termio/osc.js'
type WriteRaw = (data: string) => void
export const TerminalWriteContext = createContext<WriteRaw | null>(null)
export const TerminalWriteProvider = TerminalWriteContext.Provider
export type TerminalNotification = {
notifyITerm2: (opts: { message: string; title?: string }) => void
notifyKitty: (opts: { message: string; title: string; id: number }) => void
notifyGhostty: (opts: { message: string; title: string }) => void
notifyBell: () => void
/**
* Report progress to the terminal via OSC 9;4 sequences.
* Supported terminals: ConEmu, Ghostty 1.2.0+, iTerm2 3.6.6+
* Pass state=null to clear progress.
*/
progress: (state: Progress['state'] | null, percentage?: number) => void
}
export function useTerminalNotification(): TerminalNotification {
const writeRaw = useContext(TerminalWriteContext)
if (!writeRaw) {
throw new Error(
'useTerminalNotification must be used within TerminalWriteProvider',
)
}
const notifyITerm2 = useCallback(
({ message, title }: { message: string; title?: string }) => {
const displayString = title ? `${title}:\n${message}` : message
writeRaw(wrapForMultiplexer(osc(OSC.ITERM2, `\n\n${displayString}`)))
},
[writeRaw],
)
const notifyKitty = useCallback(
({
message,
title,
id,
}: {
message: string
title: string
id: number
}) => {
writeRaw(wrapForMultiplexer(osc(OSC.KITTY, `i=${id}:d=0:p=title`, title)))
writeRaw(wrapForMultiplexer(osc(OSC.KITTY, `i=${id}:p=body`, message)))
writeRaw(wrapForMultiplexer(osc(OSC.KITTY, `i=${id}:d=1:a=focus`, '')))
},
[writeRaw],
)
const notifyGhostty = useCallback(
({ message, title }: { message: string; title: string }) => {
writeRaw(wrapForMultiplexer(osc(OSC.GHOSTTY, 'notify', title, message)))
},
[writeRaw],
)
const notifyBell = useCallback(() => {
// Raw BEL — inside tmux this triggers tmux's bell-action (window flag).
// Wrapping would make it opaque DCS payload and lose that fallback.
writeRaw(BEL)
}, [writeRaw])
const progress = useCallback(
(state: Progress['state'] | null, percentage?: number) => {
if (!isProgressReportingAvailable()) {
return
}
if (!state) {
writeRaw(
wrapForMultiplexer(
osc(OSC.ITERM2, ITERM2.PROGRESS, PROGRESS.CLEAR, ''),
),
)
return
}
const pct = Math.max(0, Math.min(100, Math.round(percentage ?? 0)))
switch (state) {
case 'completed':
writeRaw(
wrapForMultiplexer(
osc(OSC.ITERM2, ITERM2.PROGRESS, PROGRESS.CLEAR, ''),
),
)
break
case 'error':
writeRaw(
wrapForMultiplexer(
osc(OSC.ITERM2, ITERM2.PROGRESS, PROGRESS.ERROR, pct),
),
)
break
case 'indeterminate':
writeRaw(
wrapForMultiplexer(
osc(OSC.ITERM2, ITERM2.PROGRESS, PROGRESS.INDETERMINATE, ''),
),
)
break
case 'running':
writeRaw(
wrapForMultiplexer(
osc(OSC.ITERM2, ITERM2.PROGRESS, PROGRESS.SET, pct),
),
)
break
case null:
// Handled by the if guard above
break
}
},
[writeRaw],
)
return useMemo(
() => ({ notifyITerm2, notifyKitty, notifyGhostty, notifyBell, progress }),
[notifyITerm2, notifyKitty, notifyGhostty, notifyBell, progress],
)
}