claude-code-monitor 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +83 -104
- package/dist/bin/ccm.js +38 -16
- package/dist/components/Dashboard.d.ts +6 -1
- package/dist/components/Dashboard.d.ts.map +1 -1
- package/dist/components/Dashboard.js +39 -5
- package/dist/components/SessionCard.d.ts.map +1 -1
- package/dist/components/SessionCard.js +2 -4
- package/dist/components/Spinner.js +1 -1
- package/dist/constants.d.ts +5 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +5 -2
- package/dist/hook/handler.d.ts.map +1 -1
- package/dist/hook/handler.js +16 -15
- package/dist/hooks/useServer.d.ts +10 -0
- package/dist/hooks/useServer.d.ts.map +1 -0
- package/dist/hooks/useServer.js +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +309 -0
- package/dist/store/file-store.d.ts +5 -0
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +40 -7
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/applescript.d.ts +7 -0
- package/dist/utils/applescript.d.ts.map +1 -0
- package/dist/utils/applescript.js +18 -0
- package/dist/utils/focus.d.ts +0 -1
- package/dist/utils/focus.d.ts.map +1 -1
- package/dist/utils/focus.js +16 -22
- package/dist/utils/send-text.d.ts +40 -0
- package/dist/utils/send-text.d.ts.map +1 -0
- package/dist/utils/send-text.js +324 -0
- package/dist/utils/status.js +2 -2
- package/dist/utils/stdin.d.ts +6 -0
- package/dist/utils/stdin.d.ts.map +1 -0
- package/dist/utils/stdin.js +12 -0
- package/dist/utils/terminal-strategy.d.ts +18 -0
- package/dist/utils/terminal-strategy.d.ts.map +1 -0
- package/dist/utils/terminal-strategy.js +15 -0
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +1 -3
- package/dist/utils/transcript.d.ts +10 -0
- package/dist/utils/transcript.d.ts.map +1 -0
- package/dist/utils/transcript.js +45 -0
- package/dist/utils/tty-cache.d.ts +0 -5
- package/dist/utils/tty-cache.d.ts.map +1 -1
- package/dist/utils/tty-cache.js +0 -7
- package/package.json +6 -2
- package/public/index.html +1219 -0
package/dist/utils/focus.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { executeAppleScript } from './applescript.js';
|
|
2
|
+
import { executeWithTerminalFallback } from './terminal-strategy.js';
|
|
2
3
|
/**
|
|
3
4
|
* Sanitize a string for safe use in AppleScript.
|
|
4
5
|
* Escapes backslashes, double quotes, and control characters to prevent injection.
|
|
@@ -12,25 +13,20 @@ export function sanitizeForAppleScript(str) {
|
|
|
12
13
|
.replace(/\r/g, '\\r')
|
|
13
14
|
.replace(/\t/g, '\\t');
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* TTY path pattern for validation.
|
|
18
|
+
* Matches:
|
|
19
|
+
* - macOS: /dev/ttys000, /dev/tty000
|
|
20
|
+
* - Linux: /dev/pts/0
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
const TTY_PATH_PATTERN = /^\/dev\/(ttys?\d+|pts\/\d+)$/;
|
|
15
24
|
/**
|
|
16
25
|
* Validate TTY path format.
|
|
17
|
-
* Only allows paths like /dev/ttys000, /dev/pts/0, etc.
|
|
18
26
|
* @internal
|
|
19
27
|
*/
|
|
20
28
|
export function isValidTtyPath(tty) {
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
function executeAppleScript(script) {
|
|
24
|
-
try {
|
|
25
|
-
const result = execFileSync('osascript', ['-e', script], {
|
|
26
|
-
encoding: 'utf-8',
|
|
27
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
28
|
-
}).trim();
|
|
29
|
-
return result === 'true';
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
29
|
+
return TTY_PATH_PATTERN.test(tty);
|
|
34
30
|
}
|
|
35
31
|
function buildITerm2Script(tty) {
|
|
36
32
|
const safeTty = sanitizeForAppleScript(tty);
|
|
@@ -96,13 +92,11 @@ export function focusSession(tty) {
|
|
|
96
92
|
return false;
|
|
97
93
|
if (!isValidTtyPath(tty))
|
|
98
94
|
return false;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
() =>
|
|
102
|
-
() =>
|
|
103
|
-
|
|
104
|
-
];
|
|
105
|
-
return focusStrategies.some((tryFocus) => tryFocus());
|
|
95
|
+
return executeWithTerminalFallback({
|
|
96
|
+
iTerm2: () => focusITerm2(tty),
|
|
97
|
+
terminalApp: () => focusTerminalApp(tty),
|
|
98
|
+
ghostty: () => focusGhostty(),
|
|
99
|
+
});
|
|
106
100
|
}
|
|
107
101
|
export function getSupportedTerminals() {
|
|
108
102
|
return ['iTerm2', 'Terminal.app', 'Ghostty'];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate text input for sending to terminal.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
export declare function validateTextInput(text: string): {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Send text to a terminal session and execute it (press Enter).
|
|
11
|
+
* Tries iTerm2, Terminal.app, and Ghostty in order.
|
|
12
|
+
*
|
|
13
|
+
* @param tty - The TTY path of the target terminal session
|
|
14
|
+
* @param text - The text to send to the terminal
|
|
15
|
+
* @returns true if text was sent successfully, false otherwise
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* - This is macOS only (uses AppleScript)
|
|
19
|
+
* - For iTerm2 and Terminal.app, targets specific TTY
|
|
20
|
+
* - For Ghostty, sends to the active window (TTY targeting not supported)
|
|
21
|
+
* - System Events usage for Ghostty may require accessibility permissions
|
|
22
|
+
*/
|
|
23
|
+
export declare function sendTextToTerminal(tty: string, text: string): {
|
|
24
|
+
success: boolean;
|
|
25
|
+
error?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Send a single keystroke to a terminal session.
|
|
29
|
+
* Used for responding to permission prompts (y/n/a), Ctrl+C to abort, or Escape to cancel.
|
|
30
|
+
*
|
|
31
|
+
* @param tty - The TTY path of the target terminal session
|
|
32
|
+
* @param key - Single character key to send (y, n, a, 1-9, escape, etc.)
|
|
33
|
+
* @param useControl - If true, send with Control modifier (for Ctrl+C)
|
|
34
|
+
* @returns Result object with success status
|
|
35
|
+
*/
|
|
36
|
+
export declare function sendKeystrokeToTerminal(tty: string, key: string, useControl?: boolean): {
|
|
37
|
+
success: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=send-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-text.d.ts","sourceRoot":"","sources":["../../src/utils/send-text.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAUlF;AAoPD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GACX;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAuBtC;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,UAAU,UAAQ,GACjB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAuCtC"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { executeAppleScript } from './applescript.js';
|
|
2
|
+
import { isMacOS, isValidTtyPath, sanitizeForAppleScript } from './focus.js';
|
|
3
|
+
import { executeWithTerminalFallback } from './terminal-strategy.js';
|
|
4
|
+
/**
|
|
5
|
+
* Maximum text length allowed for sending to terminal.
|
|
6
|
+
* This is a security measure to prevent accidental or malicious large inputs.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_TEXT_LENGTH = 10000;
|
|
9
|
+
/**
|
|
10
|
+
* Validate text input for sending to terminal.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function validateTextInput(text) {
|
|
14
|
+
if (!text || text.trim().length === 0) {
|
|
15
|
+
return { valid: false, error: 'Text cannot be empty' };
|
|
16
|
+
}
|
|
17
|
+
if (text.length > MAX_TEXT_LENGTH) {
|
|
18
|
+
return { valid: false, error: `Text exceeds maximum length of ${MAX_TEXT_LENGTH} characters` };
|
|
19
|
+
}
|
|
20
|
+
return { valid: true };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build AppleScript to send text to iTerm2 session by TTY.
|
|
24
|
+
* Uses clipboard and Cmd+V to paste text, then sends Enter key.
|
|
25
|
+
* This approach supports Unicode characters including Japanese.
|
|
26
|
+
*/
|
|
27
|
+
function buildITerm2SendTextScript(tty, text) {
|
|
28
|
+
const safeTty = sanitizeForAppleScript(tty);
|
|
29
|
+
const safeText = sanitizeForAppleScript(text);
|
|
30
|
+
return `
|
|
31
|
+
set the clipboard to "${safeText}"
|
|
32
|
+
tell application "iTerm2"
|
|
33
|
+
repeat with aWindow in windows
|
|
34
|
+
repeat with aTab in tabs of aWindow
|
|
35
|
+
repeat with aSession in sessions of aTab
|
|
36
|
+
if tty of aSession is "${safeTty}" then
|
|
37
|
+
select aSession
|
|
38
|
+
select aTab
|
|
39
|
+
tell aWindow to select
|
|
40
|
+
activate
|
|
41
|
+
delay 0.3
|
|
42
|
+
tell application "System Events"
|
|
43
|
+
set frontmost of process "iTerm2" to true
|
|
44
|
+
delay 0.1
|
|
45
|
+
tell process "iTerm2"
|
|
46
|
+
keystroke "v" using command down
|
|
47
|
+
delay 0.1
|
|
48
|
+
keystroke return
|
|
49
|
+
end tell
|
|
50
|
+
end tell
|
|
51
|
+
return true
|
|
52
|
+
end if
|
|
53
|
+
end repeat
|
|
54
|
+
end repeat
|
|
55
|
+
end repeat
|
|
56
|
+
return false
|
|
57
|
+
end tell
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build AppleScript to send text to Terminal.app by TTY.
|
|
62
|
+
* Uses clipboard and Cmd+V to paste text, then sends Enter key.
|
|
63
|
+
* This approach supports Unicode characters including Japanese.
|
|
64
|
+
*/
|
|
65
|
+
function buildTerminalAppSendTextScript(tty, text) {
|
|
66
|
+
const safeTty = sanitizeForAppleScript(tty);
|
|
67
|
+
const safeText = sanitizeForAppleScript(text);
|
|
68
|
+
return `
|
|
69
|
+
set the clipboard to "${safeText}"
|
|
70
|
+
tell application "Terminal"
|
|
71
|
+
repeat with aWindow in windows
|
|
72
|
+
repeat with aTab in tabs of aWindow
|
|
73
|
+
if tty of aTab is "${safeTty}" then
|
|
74
|
+
set selected of aTab to true
|
|
75
|
+
set index of aWindow to 1
|
|
76
|
+
activate
|
|
77
|
+
delay 0.2
|
|
78
|
+
tell application "System Events"
|
|
79
|
+
tell process "Terminal"
|
|
80
|
+
keystroke "v" using command down
|
|
81
|
+
delay 0.1
|
|
82
|
+
keystroke return
|
|
83
|
+
end tell
|
|
84
|
+
end tell
|
|
85
|
+
return true
|
|
86
|
+
end if
|
|
87
|
+
end repeat
|
|
88
|
+
end repeat
|
|
89
|
+
return false
|
|
90
|
+
end tell
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build AppleScript to send text to Ghostty via System Events.
|
|
95
|
+
* Ghostty doesn't support TTY-based targeting, so we send to the active window.
|
|
96
|
+
* Uses clipboard and Cmd+V to paste text, then sends Enter key.
|
|
97
|
+
* This approach supports Unicode characters including Japanese.
|
|
98
|
+
*/
|
|
99
|
+
function buildGhosttySendTextScript(text) {
|
|
100
|
+
const safeText = sanitizeForAppleScript(text);
|
|
101
|
+
return `
|
|
102
|
+
set the clipboard to "${safeText}"
|
|
103
|
+
tell application "Ghostty"
|
|
104
|
+
activate
|
|
105
|
+
end tell
|
|
106
|
+
delay 0.6
|
|
107
|
+
tell application "System Events"
|
|
108
|
+
set frontmost of process "Ghostty" to true
|
|
109
|
+
delay 0.2
|
|
110
|
+
tell process "Ghostty"
|
|
111
|
+
keystroke "v" using command down
|
|
112
|
+
delay 0.2
|
|
113
|
+
keystroke return
|
|
114
|
+
end tell
|
|
115
|
+
end tell
|
|
116
|
+
return true
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
function sendTextToITerm2(tty, text) {
|
|
120
|
+
return executeAppleScript(buildITerm2SendTextScript(tty, text));
|
|
121
|
+
}
|
|
122
|
+
function sendTextToTerminalApp(tty, text) {
|
|
123
|
+
return executeAppleScript(buildTerminalAppSendTextScript(tty, text));
|
|
124
|
+
}
|
|
125
|
+
function sendTextToGhostty(text) {
|
|
126
|
+
return executeAppleScript(buildGhosttySendTextScript(text));
|
|
127
|
+
}
|
|
128
|
+
// ============================================
|
|
129
|
+
// Direct Keystroke Functions (for permission prompts)
|
|
130
|
+
// ============================================
|
|
131
|
+
/**
|
|
132
|
+
* Build AppleScript to send a single keystroke to iTerm2 session by TTY.
|
|
133
|
+
* Used for permission prompts that expect single key input (y/n/a).
|
|
134
|
+
*/
|
|
135
|
+
function buildITerm2KeystrokeScript(tty, key, useControl = false, useKeyCode) {
|
|
136
|
+
const safeTty = sanitizeForAppleScript(tty);
|
|
137
|
+
const safeKey = sanitizeForAppleScript(key);
|
|
138
|
+
const modifiers = useControl ? ' using control down' : '';
|
|
139
|
+
const keystrokeCmd = useKeyCode !== undefined ? `key code ${useKeyCode}` : `keystroke "${safeKey}"${modifiers}`;
|
|
140
|
+
return `
|
|
141
|
+
tell application "iTerm2"
|
|
142
|
+
repeat with aWindow in windows
|
|
143
|
+
repeat with aTab in tabs of aWindow
|
|
144
|
+
repeat with aSession in sessions of aTab
|
|
145
|
+
if tty of aSession is "${safeTty}" then
|
|
146
|
+
select aSession
|
|
147
|
+
select aTab
|
|
148
|
+
tell aWindow to select
|
|
149
|
+
activate
|
|
150
|
+
delay 0.2
|
|
151
|
+
tell application "System Events"
|
|
152
|
+
set frontmost of process "iTerm2" to true
|
|
153
|
+
delay 0.1
|
|
154
|
+
tell process "iTerm2"
|
|
155
|
+
${keystrokeCmd}
|
|
156
|
+
end tell
|
|
157
|
+
end tell
|
|
158
|
+
return true
|
|
159
|
+
end if
|
|
160
|
+
end repeat
|
|
161
|
+
end repeat
|
|
162
|
+
end repeat
|
|
163
|
+
return false
|
|
164
|
+
end tell
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Build AppleScript to send a single keystroke to Terminal.app by TTY.
|
|
169
|
+
*/
|
|
170
|
+
function buildTerminalAppKeystrokeScript(tty, key, useControl = false, useKeyCode) {
|
|
171
|
+
const safeTty = sanitizeForAppleScript(tty);
|
|
172
|
+
const safeKey = sanitizeForAppleScript(key);
|
|
173
|
+
const modifiers = useControl ? ' using control down' : '';
|
|
174
|
+
const keystrokeCmd = useKeyCode !== undefined ? `key code ${useKeyCode}` : `keystroke "${safeKey}"${modifiers}`;
|
|
175
|
+
return `
|
|
176
|
+
tell application "Terminal"
|
|
177
|
+
repeat with aWindow in windows
|
|
178
|
+
repeat with aTab in tabs of aWindow
|
|
179
|
+
if tty of aTab is "${safeTty}" then
|
|
180
|
+
set selected of aTab to true
|
|
181
|
+
set index of aWindow to 1
|
|
182
|
+
activate
|
|
183
|
+
delay 0.2
|
|
184
|
+
tell application "System Events"
|
|
185
|
+
tell process "Terminal"
|
|
186
|
+
${keystrokeCmd}
|
|
187
|
+
end tell
|
|
188
|
+
end tell
|
|
189
|
+
return true
|
|
190
|
+
end if
|
|
191
|
+
end repeat
|
|
192
|
+
end repeat
|
|
193
|
+
return false
|
|
194
|
+
end tell
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Build AppleScript to send a single keystroke to Ghostty.
|
|
199
|
+
*/
|
|
200
|
+
function buildGhosttyKeystrokeScript(key, useControl = false, useKeyCode) {
|
|
201
|
+
const safeKey = sanitizeForAppleScript(key);
|
|
202
|
+
const modifiers = useControl ? ' using control down' : '';
|
|
203
|
+
const keystrokeCmd = useKeyCode !== undefined ? `key code ${useKeyCode}` : `keystroke "${safeKey}"${modifiers}`;
|
|
204
|
+
return `
|
|
205
|
+
tell application "Ghostty"
|
|
206
|
+
activate
|
|
207
|
+
end tell
|
|
208
|
+
delay 0.4
|
|
209
|
+
tell application "System Events"
|
|
210
|
+
set frontmost of process "Ghostty" to true
|
|
211
|
+
delay 0.1
|
|
212
|
+
tell process "Ghostty"
|
|
213
|
+
${keystrokeCmd}
|
|
214
|
+
end tell
|
|
215
|
+
end tell
|
|
216
|
+
return true
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
function sendKeystrokeToITerm2(tty, key, useControl = false, useKeyCode) {
|
|
220
|
+
return executeAppleScript(buildITerm2KeystrokeScript(tty, key, useControl, useKeyCode));
|
|
221
|
+
}
|
|
222
|
+
function sendKeystrokeToTerminalApp(tty, key, useControl = false, useKeyCode) {
|
|
223
|
+
return executeAppleScript(buildTerminalAppKeystrokeScript(tty, key, useControl, useKeyCode));
|
|
224
|
+
}
|
|
225
|
+
function sendKeystrokeToGhostty(key, useControl = false, useKeyCode) {
|
|
226
|
+
return executeAppleScript(buildGhosttyKeystrokeScript(key, useControl, useKeyCode));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Send text to a terminal session and execute it (press Enter).
|
|
230
|
+
* Tries iTerm2, Terminal.app, and Ghostty in order.
|
|
231
|
+
*
|
|
232
|
+
* @param tty - The TTY path of the target terminal session
|
|
233
|
+
* @param text - The text to send to the terminal
|
|
234
|
+
* @returns true if text was sent successfully, false otherwise
|
|
235
|
+
*
|
|
236
|
+
* @remarks
|
|
237
|
+
* - This is macOS only (uses AppleScript)
|
|
238
|
+
* - For iTerm2 and Terminal.app, targets specific TTY
|
|
239
|
+
* - For Ghostty, sends to the active window (TTY targeting not supported)
|
|
240
|
+
* - System Events usage for Ghostty may require accessibility permissions
|
|
241
|
+
*/
|
|
242
|
+
export function sendTextToTerminal(tty, text) {
|
|
243
|
+
if (!isMacOS()) {
|
|
244
|
+
return { success: false, error: 'This feature is only available on macOS' };
|
|
245
|
+
}
|
|
246
|
+
if (!isValidTtyPath(tty)) {
|
|
247
|
+
return { success: false, error: 'Invalid TTY path' };
|
|
248
|
+
}
|
|
249
|
+
const validation = validateTextInput(text);
|
|
250
|
+
if (!validation.valid) {
|
|
251
|
+
return { success: false, error: validation.error };
|
|
252
|
+
}
|
|
253
|
+
const success = executeWithTerminalFallback({
|
|
254
|
+
iTerm2: () => sendTextToITerm2(tty, text),
|
|
255
|
+
terminalApp: () => sendTextToTerminalApp(tty, text),
|
|
256
|
+
ghostty: () => sendTextToGhostty(text),
|
|
257
|
+
});
|
|
258
|
+
return success
|
|
259
|
+
? { success: true }
|
|
260
|
+
: { success: false, error: 'Could not send text to any terminal' };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Allowed keys for permission prompt responses.
|
|
264
|
+
*/
|
|
265
|
+
const ALLOWED_KEYS = new Set([
|
|
266
|
+
'y',
|
|
267
|
+
'n',
|
|
268
|
+
'a',
|
|
269
|
+
'1',
|
|
270
|
+
'2',
|
|
271
|
+
'3',
|
|
272
|
+
'4',
|
|
273
|
+
'5',
|
|
274
|
+
'6',
|
|
275
|
+
'7',
|
|
276
|
+
'8',
|
|
277
|
+
'9',
|
|
278
|
+
'escape',
|
|
279
|
+
]);
|
|
280
|
+
/**
|
|
281
|
+
* macOS key code for Escape key.
|
|
282
|
+
*/
|
|
283
|
+
const ESCAPE_KEY_CODE = 53;
|
|
284
|
+
/**
|
|
285
|
+
* Send a single keystroke to a terminal session.
|
|
286
|
+
* Used for responding to permission prompts (y/n/a), Ctrl+C to abort, or Escape to cancel.
|
|
287
|
+
*
|
|
288
|
+
* @param tty - The TTY path of the target terminal session
|
|
289
|
+
* @param key - Single character key to send (y, n, a, 1-9, escape, etc.)
|
|
290
|
+
* @param useControl - If true, send with Control modifier (for Ctrl+C)
|
|
291
|
+
* @returns Result object with success status
|
|
292
|
+
*/
|
|
293
|
+
export function sendKeystrokeToTerminal(tty, key, useControl = false) {
|
|
294
|
+
if (!isMacOS()) {
|
|
295
|
+
return { success: false, error: 'This feature is only available on macOS' };
|
|
296
|
+
}
|
|
297
|
+
if (!isValidTtyPath(tty)) {
|
|
298
|
+
return { success: false, error: 'Invalid TTY path' };
|
|
299
|
+
}
|
|
300
|
+
const lowerKey = key.toLowerCase();
|
|
301
|
+
const isEscapeKey = lowerKey === 'escape';
|
|
302
|
+
// Validate key input (escape is special, others must be single character)
|
|
303
|
+
if (!isEscapeKey && (!key || key.length !== 1)) {
|
|
304
|
+
return { success: false, error: 'Key must be a single character or "escape"' };
|
|
305
|
+
}
|
|
306
|
+
// Only allow specific keys for security
|
|
307
|
+
if (!useControl && !ALLOWED_KEYS.has(lowerKey)) {
|
|
308
|
+
return { success: false, error: 'Invalid key. Allowed: y, n, a, 1-9, escape' };
|
|
309
|
+
}
|
|
310
|
+
// For Ctrl+C, only allow 'c'
|
|
311
|
+
if (useControl && lowerKey !== 'c') {
|
|
312
|
+
return { success: false, error: 'Only Ctrl+C is supported' };
|
|
313
|
+
}
|
|
314
|
+
// Determine if we need to use key code (for Escape key)
|
|
315
|
+
const useKeyCode = isEscapeKey ? ESCAPE_KEY_CODE : undefined;
|
|
316
|
+
const success = executeWithTerminalFallback({
|
|
317
|
+
iTerm2: () => sendKeystrokeToITerm2(tty, key, useControl, useKeyCode),
|
|
318
|
+
terminalApp: () => sendKeystrokeToTerminalApp(tty, key, useControl, useKeyCode),
|
|
319
|
+
ghostty: () => sendKeystrokeToGhostty(key, useControl, useKeyCode),
|
|
320
|
+
});
|
|
321
|
+
return success
|
|
322
|
+
? { success: true }
|
|
323
|
+
: { success: false, error: 'Could not send keystroke to any terminal' };
|
|
324
|
+
}
|
package/dist/utils/status.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export function getStatusDisplay(status) {
|
|
2
2
|
switch (status) {
|
|
3
3
|
case 'running':
|
|
4
|
-
return { symbol: '●', color: '
|
|
4
|
+
return { symbol: '●', color: 'gray', label: 'Running' };
|
|
5
5
|
case 'waiting_input':
|
|
6
6
|
return { symbol: '◐', color: 'yellow', label: 'Waiting' };
|
|
7
7
|
case 'stopped':
|
|
8
|
-
return { symbol: '✓', color: '
|
|
8
|
+
return { symbol: '✓', color: 'green', label: 'Done' };
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdin.d.ts","sourceRoot":"","sources":["../../src/utils/stdin.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAOvD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read all data from stdin and parse as JSON.
|
|
3
|
+
* @throws {SyntaxError} If the input is not valid JSON
|
|
4
|
+
*/
|
|
5
|
+
export async function readJsonFromStdin() {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
for await (const chunk of process.stdin) {
|
|
8
|
+
chunks.push(chunk);
|
|
9
|
+
}
|
|
10
|
+
const input = Buffer.concat(chunks).toString('utf-8');
|
|
11
|
+
return JSON.parse(input);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal operation strategies for macOS.
|
|
3
|
+
* Provides a unified interface to execute operations across different terminal apps.
|
|
4
|
+
*/
|
|
5
|
+
export interface TerminalOperations {
|
|
6
|
+
iTerm2: () => boolean;
|
|
7
|
+
terminalApp: () => boolean;
|
|
8
|
+
ghostty: () => boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Execute terminal operation with fallback strategy.
|
|
12
|
+
* Tries iTerm2 → Terminal.app → Ghostty in order, returning on first success.
|
|
13
|
+
*
|
|
14
|
+
* @param operations - Terminal-specific operation functions
|
|
15
|
+
* @returns true if any terminal operation succeeded, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeWithTerminalFallback(operations: TerminalOperations): boolean;
|
|
18
|
+
//# sourceMappingURL=terminal-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-strategy.d.ts","sourceRoot":"","sources":["../../src/utils/terminal-strategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAGnF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal operation strategies for macOS.
|
|
3
|
+
* Provides a unified interface to execute operations across different terminal apps.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Execute terminal operation with fallback strategy.
|
|
7
|
+
* Tries iTerm2 → Terminal.app → Ghostty in order, returning on first success.
|
|
8
|
+
*
|
|
9
|
+
* @param operations - Terminal-specific operation functions
|
|
10
|
+
* @returns true if any terminal operation succeeded, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
export function executeWithTerminalFallback(operations) {
|
|
13
|
+
const strategies = [operations.iTerm2, operations.terminalApp, operations.ghostty];
|
|
14
|
+
return strategies.some((op) => op());
|
|
15
|
+
}
|
package/dist/utils/time.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAa5D"}
|
package/dist/utils/time.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build transcript file path from cwd and session_id.
|
|
3
|
+
* Claude Code stores transcripts at ~/.claude/projects/{encoded-cwd}/{session_id}.jsonl
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildTranscriptPath(cwd: string, sessionId: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get the last assistant text message from a transcript file.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getLastAssistantMessage(transcriptPath: string): string | undefined;
|
|
10
|
+
//# sourceMappingURL=transcript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/utils/transcript.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAI1E;AAOD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA+BlF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Build transcript file path from cwd and session_id.
|
|
6
|
+
* Claude Code stores transcripts at ~/.claude/projects/{encoded-cwd}/{session_id}.jsonl
|
|
7
|
+
*/
|
|
8
|
+
export function buildTranscriptPath(cwd, sessionId) {
|
|
9
|
+
// Encode cwd: replace / and . with - (including leading /)
|
|
10
|
+
const encodedCwd = cwd.replace(/[/.]/g, '-');
|
|
11
|
+
return join(homedir(), '.claude', 'projects', encodedCwd, `${sessionId}.jsonl`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the last assistant text message from a transcript file.
|
|
15
|
+
*/
|
|
16
|
+
export function getLastAssistantMessage(transcriptPath) {
|
|
17
|
+
if (!transcriptPath || !existsSync(transcriptPath)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const content = readFileSync(transcriptPath, 'utf-8');
|
|
22
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
23
|
+
// Read from end to find last text message
|
|
24
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
25
|
+
try {
|
|
26
|
+
const entry = JSON.parse(lines[i]);
|
|
27
|
+
if (entry.type === 'assistant' && entry.message?.content) {
|
|
28
|
+
const contentBlocks = entry.message.content;
|
|
29
|
+
for (const block of contentBlocks) {
|
|
30
|
+
if (block.type === 'text' && block.text) {
|
|
31
|
+
return block.text;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Skip invalid JSON lines
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignore file read errors
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tty-cache.d.ts","sourceRoot":"","sources":["../../src/utils/tty-cache.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAsB3D
|
|
1
|
+
{"version":3,"file":"tty-cache.d.ts","sourceRoot":"","sources":["../../src/utils/tty-cache.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAsB3D"}
|
package/dist/utils/tty-cache.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-monitor",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI for monitoring multiple Claude Code sessions in real-time",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
+
"public",
|
|
19
20
|
"README.md",
|
|
20
21
|
"LICENSE",
|
|
21
22
|
"CHANGELOG.md"
|
|
@@ -63,12 +64,15 @@
|
|
|
63
64
|
"chokidar": "^4.0.3",
|
|
64
65
|
"commander": "^13.1.0",
|
|
65
66
|
"ink": "^5.2.0",
|
|
66
|
-
"
|
|
67
|
+
"qrcode-terminal": "^0.12.0",
|
|
68
|
+
"react": "^18.3.1",
|
|
69
|
+
"ws": "^8.19.0"
|
|
67
70
|
},
|
|
68
71
|
"devDependencies": {
|
|
69
72
|
"@biomejs/biome": "^2.3.11",
|
|
70
73
|
"@types/node": "^22.15.30",
|
|
71
74
|
"@types/react": "^18.3.12",
|
|
75
|
+
"@types/ws": "^8.18.1",
|
|
72
76
|
"@vitest/coverage-v8": "^4.0.17",
|
|
73
77
|
"tsx": "^4.20.3",
|
|
74
78
|
"typescript": "^5.8.3",
|