claude-code-monitor 1.0.0 → 1.0.2
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 +18 -0
- package/dist/components/SessionCard.d.ts.map +1 -1
- package/dist/components/SessionCard.js +2 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +8 -0
- package/dist/hook/handler.d.ts.map +1 -1
- package/dist/hook/handler.js +3 -1
- package/dist/hooks/useSessions.d.ts.map +1 -1
- package/dist/hooks/useSessions.js +35 -22
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js +4 -1
- package/dist/store/file-store.d.ts +4 -0
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +49 -4
- package/dist/utils/tty-cache.d.ts.map +1 -1
- package/dist/utils/tty-cache.js +22 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.2] - 2026-01-17
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Handle undefined cwd in session display (shows "(unknown)" instead of crashing)
|
|
13
|
+
- Ensure hook data is written before process exits
|
|
14
|
+
|
|
15
|
+
### Security
|
|
16
|
+
|
|
17
|
+
- Set file permission 0o600 for settings.json
|
|
18
|
+
|
|
19
|
+
## [1.0.1] - 2026-01-17
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Improve performance with debounced file writes and session updates
|
|
24
|
+
- Add TTY cache size limit to prevent memory growth
|
|
25
|
+
|
|
8
26
|
## [1.0.0] - 2026-01-17
|
|
9
27
|
|
|
10
28
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.d.ts","sourceRoot":"","sources":["../../src/components/SessionCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"SessionCard.d.ts","sourceRoot":"","sources":["../../src/components/SessionCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB;AAOD,eAAO,MAAM,WAAW,8CAiCtB,CAAC"}
|
|
@@ -5,6 +5,8 @@ import { getStatusDisplay } from '../utils/status.js';
|
|
|
5
5
|
import { formatRelativeTime } from '../utils/time.js';
|
|
6
6
|
import { Spinner } from './Spinner.js';
|
|
7
7
|
function abbreviateHomePath(path) {
|
|
8
|
+
if (!path)
|
|
9
|
+
return '(unknown)';
|
|
8
10
|
return path.replace(/^\/Users\/[^/]+/, '~');
|
|
9
11
|
}
|
|
10
12
|
export const SessionCard = memo(function SessionCard({ session, index, isSelected, }) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export declare const PACKAGE_NAME = "claude-code-monitor";
|
|
|
7
7
|
export declare const SESSION_TIMEOUT_MS: number;
|
|
8
8
|
/** TTY cache TTL in milliseconds (30 seconds) */
|
|
9
9
|
export declare const TTY_CACHE_TTL_MS = 30000;
|
|
10
|
+
/** Maximum number of entries in TTY cache */
|
|
11
|
+
export declare const MAX_TTY_CACHE_SIZE = 100;
|
|
12
|
+
/** Debounce delay for useSessions updates in milliseconds */
|
|
13
|
+
export declare const SESSION_UPDATE_DEBOUNCE_MS = 150;
|
|
14
|
+
/** Debounce delay for JSON file writes in milliseconds */
|
|
15
|
+
export declare const WRITE_DEBOUNCE_MS = 100;
|
|
16
|
+
/** Periodic refresh interval for timeout detection in milliseconds (60 seconds) */
|
|
17
|
+
export declare const SESSION_REFRESH_INTERVAL_MS = 60000;
|
|
10
18
|
/** Hook event types supported by Claude Code */
|
|
11
19
|
export declare const HOOK_EVENTS: readonly ["UserPromptSubmit", "PreToolUse", "PostToolUse", "Notification", "Stop"];
|
|
12
20
|
export type HookEventName = (typeof HOOK_EVENTS)[number];
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yCAAyC;AACzC,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAElD,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AAEjD,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,gDAAgD;AAChD,eAAO,MAAM,WAAW,oFAMd,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yCAAyC;AACzC,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAElD,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AAEjD,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,gDAAgD;AAChD,eAAO,MAAM,WAAW,oFAMd,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -7,6 +7,14 @@ export const PACKAGE_NAME = 'claude-code-monitor';
|
|
|
7
7
|
export const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
8
8
|
/** TTY cache TTL in milliseconds (30 seconds) */
|
|
9
9
|
export const TTY_CACHE_TTL_MS = 30_000;
|
|
10
|
+
/** Maximum number of entries in TTY cache */
|
|
11
|
+
export const MAX_TTY_CACHE_SIZE = 100;
|
|
12
|
+
/** Debounce delay for useSessions updates in milliseconds */
|
|
13
|
+
export const SESSION_UPDATE_DEBOUNCE_MS = 150;
|
|
14
|
+
/** Debounce delay for JSON file writes in milliseconds */
|
|
15
|
+
export const WRITE_DEBOUNCE_MS = 100;
|
|
16
|
+
/** Periodic refresh interval for timeout detection in milliseconds (60 seconds) */
|
|
17
|
+
export const SESSION_REFRESH_INTERVAL_MS = 60_000;
|
|
10
18
|
/** Hook event types supported by Claude Code */
|
|
11
19
|
export const HOOK_EVENTS = [
|
|
12
20
|
'UserPromptSubmit',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/hook/handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlE,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAMhD,CAAC;AAEH,gBAAgB;AAChB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAExE;AAED,gBAAgB;AAChB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/hook/handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlE,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAMhD,CAAC;AAEH,gBAAgB;AAChB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAExE;AAED,gBAAgB;AAChB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDpF"}
|
package/dist/hook/handler.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { updateSession } from '../store/file-store.js';
|
|
1
|
+
import { flushPendingWrites, updateSession } from '../store/file-store.js';
|
|
2
2
|
// Allowed hook event names (whitelist)
|
|
3
3
|
/** @internal */
|
|
4
4
|
export const VALID_HOOK_EVENTS = new Set([
|
|
@@ -59,4 +59,6 @@ export async function handleHookEvent(eventName, tty) {
|
|
|
59
59
|
notification_type: hookPayload.notification_type,
|
|
60
60
|
};
|
|
61
61
|
updateSession(event);
|
|
62
|
+
// Ensure data is written before process exits (hooks are short-lived processes)
|
|
63
|
+
flushPendingWrites();
|
|
62
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSessions.d.ts","sourceRoot":"","sources":["../../src/hooks/useSessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSessions.d.ts","sourceRoot":"","sources":["../../src/hooks/useSessions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,wBAAgB,WAAW,IAAI;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB,CAuDA"}
|
|
@@ -1,41 +1,54 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { SESSION_REFRESH_INTERVAL_MS, SESSION_UPDATE_DEBOUNCE_MS } from '../constants.js';
|
|
3
4
|
import { getSessions, getStorePath } from '../store/file-store.js';
|
|
4
|
-
const REFRESH_INTERVAL_MS = 60_000; // タイムアウト検出のための定期リフレッシュ(chokidarが主で、これはバックアップ)
|
|
5
5
|
export function useSessions() {
|
|
6
6
|
const [sessions, setSessions] = useState([]);
|
|
7
7
|
const [loading, setLoading] = useState(true);
|
|
8
8
|
const [error, setError] = useState(null);
|
|
9
|
+
const debounceTimerRef = useRef(null);
|
|
10
|
+
const loadSessions = useCallback(() => {
|
|
11
|
+
try {
|
|
12
|
+
const data = getSessions();
|
|
13
|
+
setSessions(data);
|
|
14
|
+
setError(null);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
setError(e instanceof Error ? e : new Error('Failed to load sessions'));
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
setLoading(false);
|
|
21
|
+
}
|
|
22
|
+
}, []);
|
|
23
|
+
const debouncedLoadSessions = useCallback(() => {
|
|
24
|
+
if (debounceTimerRef.current) {
|
|
25
|
+
clearTimeout(debounceTimerRef.current);
|
|
26
|
+
}
|
|
27
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
28
|
+
loadSessions();
|
|
29
|
+
debounceTimerRef.current = null;
|
|
30
|
+
}, SESSION_UPDATE_DEBOUNCE_MS);
|
|
31
|
+
}, [loadSessions]);
|
|
9
32
|
useEffect(() => {
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
const data = getSessions();
|
|
13
|
-
setSessions(data);
|
|
14
|
-
setError(null);
|
|
15
|
-
}
|
|
16
|
-
catch (e) {
|
|
17
|
-
setError(e instanceof Error ? e : new Error('Failed to load sessions'));
|
|
18
|
-
}
|
|
19
|
-
finally {
|
|
20
|
-
setLoading(false);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
// Initial load
|
|
33
|
+
// Initial load (immediate, no debounce)
|
|
24
34
|
loadSessions();
|
|
25
|
-
// Watch file changes
|
|
35
|
+
// Watch file changes (debounced)
|
|
26
36
|
const storePath = getStorePath();
|
|
27
37
|
const watcher = chokidar.watch(storePath, {
|
|
28
38
|
persistent: true,
|
|
29
39
|
ignoreInitial: true,
|
|
30
40
|
});
|
|
31
|
-
watcher.on('change',
|
|
32
|
-
watcher.on('add',
|
|
33
|
-
// Periodic refresh
|
|
34
|
-
const interval = setInterval(loadSessions,
|
|
41
|
+
watcher.on('change', debouncedLoadSessions);
|
|
42
|
+
watcher.on('add', debouncedLoadSessions);
|
|
43
|
+
// Periodic refresh for timeout detection (chokidar is primary, this is backup)
|
|
44
|
+
const interval = setInterval(loadSessions, SESSION_REFRESH_INTERVAL_MS);
|
|
35
45
|
return () => {
|
|
36
46
|
watcher.close();
|
|
37
47
|
clearInterval(interval);
|
|
48
|
+
if (debounceTimerRef.current) {
|
|
49
|
+
clearTimeout(debounceTimerRef.current);
|
|
50
|
+
}
|
|
38
51
|
};
|
|
39
|
-
}, []);
|
|
52
|
+
}, [loadSessions, debouncedLoadSessions]);
|
|
40
53
|
return { sessions, loading, error };
|
|
41
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAUA,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gBAAgB;AAChB,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,gBAAgB;AAChB,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAUD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAG/F;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,CAcjF;AAkBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAazF;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAUA,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gBAAgB;AAChB,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,gBAAgB;AAChB,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAUD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAG/F;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,CAcjF;AAkBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAazF;AAkDD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAwB3C;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAyChD"}
|
package/dist/setup/index.js
CHANGED
|
@@ -120,7 +120,10 @@ function applyHooks(settings, hooksToAdd, baseCommand) {
|
|
|
120
120
|
existing.push(createHookEntry(eventName, baseCommand));
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2),
|
|
123
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), {
|
|
124
|
+
encoding: 'utf-8',
|
|
125
|
+
mode: 0o600,
|
|
126
|
+
});
|
|
124
127
|
}
|
|
125
128
|
/**
|
|
126
129
|
* Check if hooks are already configured
|
|
@@ -2,6 +2,10 @@ import type { HookEvent, Session, SessionStatus, StoreData } from '../types/inde
|
|
|
2
2
|
export { isTtyAlive } from '../utils/tty-cache.js';
|
|
3
3
|
export declare function readStore(): StoreData;
|
|
4
4
|
export declare function writeStore(data: StoreData): void;
|
|
5
|
+
/** Immediately flush any pending writes (useful for testing and cleanup) */
|
|
6
|
+
export declare function flushPendingWrites(): void;
|
|
7
|
+
/** Reset the in-memory cache (useful for testing) */
|
|
8
|
+
export declare function resetStoreCache(): void;
|
|
5
9
|
/** @internal */
|
|
6
10
|
export declare function getSessionKey(sessionId: string, tty?: string): string;
|
|
7
11
|
/** @internal */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../src/store/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAItF,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../src/store/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAItF,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAsBnD,wBAAgB,SAAS,IAAI,SAAS,CAgBrC;AAoBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAQhD;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAED,qDAAqD;AACrD,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,gBAAgB;AAChB,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,GACV,IAAI,CAMN;AAED,gBAAgB;AAChB,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,aAAa,CA8B9F;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAyBvD;AAED,wBAAgB,WAAW,IAAI,OAAO,EAAE,CAwBvC;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAI/E;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAKnE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
|
package/dist/store/file-store.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { SESSION_TIMEOUT_MS } from '../constants.js';
|
|
4
|
+
import { SESSION_TIMEOUT_MS, WRITE_DEBOUNCE_MS } from '../constants.js';
|
|
5
5
|
import { isTtyAlive } from '../utils/tty-cache.js';
|
|
6
6
|
// Re-export for backward compatibility
|
|
7
7
|
export { isTtyAlive } from '../utils/tty-cache.js';
|
|
8
8
|
const STORE_DIR = join(homedir(), '.claude-monitor');
|
|
9
9
|
const STORE_FILE = join(STORE_DIR, 'sessions.json');
|
|
10
|
+
// In-memory cache for batched writes
|
|
11
|
+
let cachedStore = null;
|
|
12
|
+
let writeTimer = null;
|
|
10
13
|
function ensureStoreDir() {
|
|
11
14
|
if (!existsSync(STORE_DIR)) {
|
|
12
15
|
mkdirSync(STORE_DIR, { recursive: true, mode: 0o700 });
|
|
@@ -19,6 +22,10 @@ function getEmptyStoreData() {
|
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
export function readStore() {
|
|
25
|
+
// Return cached data if available (for batched writes consistency)
|
|
26
|
+
if (cachedStore) {
|
|
27
|
+
return cachedStore;
|
|
28
|
+
}
|
|
22
29
|
ensureStoreDir();
|
|
23
30
|
if (!existsSync(STORE_FILE)) {
|
|
24
31
|
return getEmptyStoreData();
|
|
@@ -31,10 +38,48 @@ export function readStore() {
|
|
|
31
38
|
return getEmptyStoreData();
|
|
32
39
|
}
|
|
33
40
|
}
|
|
41
|
+
function flushWrite() {
|
|
42
|
+
if (cachedStore) {
|
|
43
|
+
try {
|
|
44
|
+
ensureStoreDir();
|
|
45
|
+
cachedStore.updated_at = new Date().toISOString();
|
|
46
|
+
writeFileSync(STORE_FILE, JSON.stringify(cachedStore), { encoding: 'utf-8', mode: 0o600 });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Silently ignore write errors to avoid crashing the hook process
|
|
50
|
+
// Data loss is acceptable as session data is ephemeral
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
cachedStore = null;
|
|
54
|
+
writeTimer = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
writeTimer = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
34
61
|
export function writeStore(data) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
cachedStore = data;
|
|
63
|
+
// Cancel previous timer and schedule new write
|
|
64
|
+
if (writeTimer) {
|
|
65
|
+
clearTimeout(writeTimer);
|
|
66
|
+
}
|
|
67
|
+
writeTimer = setTimeout(flushWrite, WRITE_DEBOUNCE_MS);
|
|
68
|
+
}
|
|
69
|
+
/** Immediately flush any pending writes (useful for testing and cleanup) */
|
|
70
|
+
export function flushPendingWrites() {
|
|
71
|
+
if (writeTimer) {
|
|
72
|
+
clearTimeout(writeTimer);
|
|
73
|
+
flushWrite();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Reset the in-memory cache (useful for testing) */
|
|
77
|
+
export function resetStoreCache() {
|
|
78
|
+
if (writeTimer) {
|
|
79
|
+
clearTimeout(writeTimer);
|
|
80
|
+
writeTimer = null;
|
|
81
|
+
}
|
|
82
|
+
cachedStore = null;
|
|
38
83
|
}
|
|
39
84
|
/** @internal */
|
|
40
85
|
export function getSessionKey(sessionId, tty) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tty-cache.d.ts","sourceRoot":"","sources":["../../src/utils/tty-cache.ts"],"names":[],"mappings":"
|
|
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;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
|
package/dist/utils/tty-cache.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { statSync } from 'node:fs';
|
|
2
|
-
import { TTY_CACHE_TTL_MS } from '../constants.js';
|
|
2
|
+
import { MAX_TTY_CACHE_SIZE, TTY_CACHE_TTL_MS } from '../constants.js';
|
|
3
3
|
// TTY check cache to avoid repeated statSync calls
|
|
4
4
|
const ttyCache = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Evict oldest entries when cache exceeds max size
|
|
7
|
+
* Uses FIFO eviction based on checkedAt timestamp
|
|
8
|
+
*/
|
|
9
|
+
function evictOldestIfNeeded() {
|
|
10
|
+
if (ttyCache.size <= MAX_TTY_CACHE_SIZE) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Find and remove oldest entries until we're under the limit
|
|
14
|
+
const entriesToRemove = ttyCache.size - MAX_TTY_CACHE_SIZE;
|
|
15
|
+
const sortedEntries = [...ttyCache.entries()].sort((a, b) => a[1].checkedAt - b[1].checkedAt);
|
|
16
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
17
|
+
ttyCache.delete(sortedEntries[i][0]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
5
20
|
/**
|
|
6
21
|
* Check if a TTY device is still alive (exists in filesystem)
|
|
7
22
|
* Results are cached for TTY_CACHE_TTL_MS to avoid repeated stat calls
|
|
@@ -17,15 +32,17 @@ export function isTtyAlive(tty) {
|
|
|
17
32
|
return cached.alive;
|
|
18
33
|
}
|
|
19
34
|
// Check TTY and cache result
|
|
35
|
+
let alive;
|
|
20
36
|
try {
|
|
21
37
|
statSync(tty);
|
|
22
|
-
|
|
23
|
-
return true;
|
|
38
|
+
alive = true;
|
|
24
39
|
}
|
|
25
40
|
catch {
|
|
26
|
-
|
|
27
|
-
return false;
|
|
41
|
+
alive = false;
|
|
28
42
|
}
|
|
43
|
+
ttyCache.set(tty, { alive, checkedAt: now });
|
|
44
|
+
evictOldestIfNeeded();
|
|
45
|
+
return alive;
|
|
29
46
|
}
|
|
30
47
|
/**
|
|
31
48
|
* Clear the TTY cache (useful for testing)
|