ccmanager 2.11.5 → 3.0.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/dist/components/Configuration.js +14 -0
- package/dist/components/ConfigureCustomCommand.d.ts +9 -0
- package/dist/components/ConfigureCustomCommand.js +44 -0
- package/dist/components/ConfigureOther.d.ts +6 -0
- package/dist/components/ConfigureOther.js +87 -0
- package/dist/components/ConfigureOther.test.d.ts +1 -0
- package/dist/components/ConfigureOther.test.js +80 -0
- package/dist/components/ConfigureStatusHooks.js +7 -1
- package/dist/components/CustomCommandSummary.d.ts +6 -0
- package/dist/components/CustomCommandSummary.js +10 -0
- package/dist/components/Menu.recent-projects.test.js +2 -0
- package/dist/components/Menu.test.js +2 -0
- package/dist/components/Session.d.ts +2 -2
- package/dist/components/Session.js +91 -8
- package/dist/constants/statusIcons.d.ts +3 -1
- package/dist/constants/statusIcons.js +3 -0
- package/dist/services/autoApprovalVerifier.d.ts +25 -0
- package/dist/services/autoApprovalVerifier.js +265 -0
- package/dist/services/autoApprovalVerifier.test.d.ts +1 -0
- package/dist/services/autoApprovalVerifier.test.js +120 -0
- package/dist/services/configurationManager.d.ts +7 -0
- package/dist/services/configurationManager.js +35 -0
- package/dist/services/sessionManager.autoApproval.test.d.ts +1 -0
- package/dist/services/sessionManager.autoApproval.test.js +160 -0
- package/dist/services/sessionManager.d.ts +5 -0
- package/dist/services/sessionManager.js +149 -1
- package/dist/services/sessionManager.statePersistence.test.js +2 -0
- package/dist/services/sessionManager.test.js +6 -0
- package/dist/types/index.d.ts +14 -1
- package/dist/utils/hookExecutor.test.js +8 -0
- package/dist/utils/logger.d.ts +83 -14
- package/dist/utils/logger.js +218 -17
- package/dist/utils/worktreeUtils.test.js +1 -0
- package/package.json +1 -1
|
@@ -9,8 +9,11 @@ import { createStateDetector } from './stateDetector.js';
|
|
|
9
9
|
import { STATE_PERSISTENCE_DURATION_MS, STATE_CHECK_INTERVAL_MS, } from '../constants/statePersistence.js';
|
|
10
10
|
import { Effect } from 'effect';
|
|
11
11
|
import { ProcessError, ConfigError } from '../types/errors.js';
|
|
12
|
+
import { autoApprovalVerifier } from './autoApprovalVerifier.js';
|
|
13
|
+
import { logger } from '../utils/logger.js';
|
|
12
14
|
const { Terminal } = pkg;
|
|
13
15
|
const execAsync = promisify(exec);
|
|
16
|
+
const TERMINAL_CONTENT_MAX_LINES = 300;
|
|
14
17
|
export class SessionManager extends EventEmitter {
|
|
15
18
|
async spawn(command, args, worktreePath) {
|
|
16
19
|
const spawnOptions = {
|
|
@@ -26,7 +29,104 @@ export class SessionManager extends EventEmitter {
|
|
|
26
29
|
// Create a detector based on the session's detection strategy
|
|
27
30
|
const strategy = session.detectionStrategy || 'claude';
|
|
28
31
|
const detector = createStateDetector(strategy);
|
|
29
|
-
|
|
32
|
+
const detectedState = detector.detectState(session.terminal, session.state);
|
|
33
|
+
// If auto-approval is enabled and state is waiting_input, convert to pending_auto_approval
|
|
34
|
+
if (detectedState === 'waiting_input' &&
|
|
35
|
+
configurationManager.isAutoApprovalEnabled() &&
|
|
36
|
+
!session.autoApprovalFailed) {
|
|
37
|
+
return 'pending_auto_approval';
|
|
38
|
+
}
|
|
39
|
+
return detectedState;
|
|
40
|
+
}
|
|
41
|
+
getTerminalContent(session) {
|
|
42
|
+
const buffer = session.terminal.buffer.active;
|
|
43
|
+
const lines = [];
|
|
44
|
+
// Start from the bottom and work our way up
|
|
45
|
+
for (let i = buffer.length - 1; i >= 0 && lines.length < TERMINAL_CONTENT_MAX_LINES; i--) {
|
|
46
|
+
const line = buffer.getLine(i);
|
|
47
|
+
if (line) {
|
|
48
|
+
const text = line.translateToString(true);
|
|
49
|
+
// Skip empty lines at the bottom
|
|
50
|
+
if (lines.length > 0 || text.trim() !== '') {
|
|
51
|
+
lines.unshift(text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
handleAutoApproval(session) {
|
|
58
|
+
// Cancel any existing verification before starting a new one
|
|
59
|
+
this.cancelAutoApprovalVerification(session, 'Restarting verification for pending auto-approval state');
|
|
60
|
+
const abortController = new AbortController();
|
|
61
|
+
session.autoApprovalAbortController = abortController;
|
|
62
|
+
session.autoApprovalReason = undefined;
|
|
63
|
+
// Get terminal content for verification
|
|
64
|
+
const terminalContent = this.getTerminalContent(session);
|
|
65
|
+
// Verify if permission is needed
|
|
66
|
+
void Effect.runPromise(autoApprovalVerifier.verifyNeedsPermission(terminalContent, {
|
|
67
|
+
signal: abortController.signal,
|
|
68
|
+
}))
|
|
69
|
+
.then(autoApprovalResult => {
|
|
70
|
+
if (abortController.signal.aborted) {
|
|
71
|
+
logger.debug(`[${session.id}] Auto-approval verification aborted before completion`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// If state already moved away, skip handling
|
|
75
|
+
if (session.state !== 'pending_auto_approval') {
|
|
76
|
+
logger.debug(`[${session.id}] Skipping auto-approval handling; current state is ${session.state}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (autoApprovalResult.needsPermission) {
|
|
80
|
+
// Change state to waiting_input to ask for user permission
|
|
81
|
+
logger.info(`[${session.id}] Auto-approval verification determined user permission needed`);
|
|
82
|
+
session.state = 'waiting_input';
|
|
83
|
+
session.autoApprovalFailed = true;
|
|
84
|
+
session.autoApprovalReason = autoApprovalResult.reason;
|
|
85
|
+
session.pendingState = undefined;
|
|
86
|
+
session.pendingStateStart = undefined;
|
|
87
|
+
this.emit('sessionStateChanged', session);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Auto-approve by simulating Enter key press
|
|
91
|
+
logger.info(`[${session.id}] Auto-approval granted, simulating user permission`);
|
|
92
|
+
session.autoApprovalReason = undefined;
|
|
93
|
+
session.process.write('\r');
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.catch((error) => {
|
|
97
|
+
if (abortController.signal.aborted) {
|
|
98
|
+
logger.debug(`[${session.id}] Auto-approval verification aborted (${error?.message ?? 'aborted'})`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// On failure, fall back to requiring explicit permission
|
|
102
|
+
logger.error(`[${session.id}] Auto-approval verification failed, requiring user permission`, error);
|
|
103
|
+
if (session.state === 'pending_auto_approval') {
|
|
104
|
+
session.state = 'waiting_input';
|
|
105
|
+
session.autoApprovalFailed = true;
|
|
106
|
+
session.autoApprovalReason =
|
|
107
|
+
error?.message ??
|
|
108
|
+
'Auto-approval verification failed';
|
|
109
|
+
session.pendingState = undefined;
|
|
110
|
+
session.pendingStateStart = undefined;
|
|
111
|
+
this.emit('sessionStateChanged', session);
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.finally(() => {
|
|
115
|
+
if (session.autoApprovalAbortController === abortController) {
|
|
116
|
+
session.autoApprovalAbortController = undefined;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
cancelAutoApprovalVerification(session, reason) {
|
|
121
|
+
const controller = session.autoApprovalAbortController;
|
|
122
|
+
if (!controller) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!controller.signal.aborted) {
|
|
126
|
+
controller.abort();
|
|
127
|
+
}
|
|
128
|
+
session.autoApprovalAbortController = undefined;
|
|
129
|
+
logger.info(`[${session.id}] Cancelled auto-approval verification: ${reason}`);
|
|
30
130
|
}
|
|
31
131
|
constructor() {
|
|
32
132
|
super();
|
|
@@ -81,6 +181,9 @@ export class SessionManager extends EventEmitter {
|
|
|
81
181
|
devcontainerConfig: options.devcontainerConfig ?? undefined,
|
|
82
182
|
pendingState: undefined,
|
|
83
183
|
pendingStateStart: undefined,
|
|
184
|
+
autoApprovalFailed: false,
|
|
185
|
+
autoApprovalReason: undefined,
|
|
186
|
+
autoApprovalAbortController: undefined,
|
|
84
187
|
};
|
|
85
188
|
// Set up persistent background data handler for state detection
|
|
86
189
|
this.setupBackgroundHandler(session);
|
|
@@ -266,6 +369,22 @@ export class SessionManager extends EventEmitter {
|
|
|
266
369
|
session.state = detectedState;
|
|
267
370
|
session.pendingState = undefined;
|
|
268
371
|
session.pendingStateStart = undefined;
|
|
372
|
+
if (session.autoApprovalAbortController &&
|
|
373
|
+
detectedState !== 'pending_auto_approval') {
|
|
374
|
+
this.cancelAutoApprovalVerification(session, `state changed to ${detectedState}`);
|
|
375
|
+
}
|
|
376
|
+
// If we previously blocked auto-approval and have moved out of a user prompt,
|
|
377
|
+
// allow future auto-approval attempts.
|
|
378
|
+
if (session.autoApprovalFailed &&
|
|
379
|
+
detectedState !== 'waiting_input' &&
|
|
380
|
+
detectedState !== 'pending_auto_approval') {
|
|
381
|
+
session.autoApprovalFailed = false;
|
|
382
|
+
session.autoApprovalReason = undefined;
|
|
383
|
+
}
|
|
384
|
+
// Handle auto-approval if state is pending_auto_approval
|
|
385
|
+
if (detectedState === 'pending_auto_approval') {
|
|
386
|
+
this.handleAutoApproval(session);
|
|
387
|
+
}
|
|
269
388
|
// Execute status hook asynchronously (non-blocking) using Effect
|
|
270
389
|
void Effect.runPromise(executeStatusHook(oldState, detectedState, session));
|
|
271
390
|
this.emit('sessionStateChanged', session);
|
|
@@ -282,6 +401,9 @@ export class SessionManager extends EventEmitter {
|
|
|
282
401
|
this.setupExitHandler(session);
|
|
283
402
|
}
|
|
284
403
|
cleanupSession(session) {
|
|
404
|
+
if (session.autoApprovalAbortController) {
|
|
405
|
+
this.cancelAutoApprovalVerification(session, 'Session cleanup');
|
|
406
|
+
}
|
|
285
407
|
// Clear the state check interval
|
|
286
408
|
if (session.stateCheckInterval) {
|
|
287
409
|
clearInterval(session.stateCheckInterval);
|
|
@@ -313,9 +435,31 @@ export class SessionManager extends EventEmitter {
|
|
|
313
435
|
}
|
|
314
436
|
}
|
|
315
437
|
}
|
|
438
|
+
cancelAutoApproval(worktreePath, reason = 'User input received') {
|
|
439
|
+
const session = this.sessions.get(worktreePath);
|
|
440
|
+
if (!session) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (session.state !== 'pending_auto_approval' &&
|
|
444
|
+
!session.autoApprovalAbortController) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this.cancelAutoApprovalVerification(session, reason);
|
|
448
|
+
session.autoApprovalFailed = true;
|
|
449
|
+
session.autoApprovalReason = reason;
|
|
450
|
+
session.pendingState = undefined;
|
|
451
|
+
session.pendingStateStart = undefined;
|
|
452
|
+
if (session.state === 'pending_auto_approval') {
|
|
453
|
+
session.state = 'waiting_input';
|
|
454
|
+
this.emit('sessionStateChanged', session);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
316
457
|
destroySession(worktreePath) {
|
|
317
458
|
const session = this.sessions.get(worktreePath);
|
|
318
459
|
if (session) {
|
|
460
|
+
if (session.autoApprovalAbortController) {
|
|
461
|
+
this.cancelAutoApprovalVerification(session, 'Session destroyed');
|
|
462
|
+
}
|
|
319
463
|
// Clear the state check interval
|
|
320
464
|
if (session.stateCheckInterval) {
|
|
321
465
|
clearInterval(session.stateCheckInterval);
|
|
@@ -493,6 +637,7 @@ export class SessionManager extends EventEmitter {
|
|
|
493
637
|
idle: 0,
|
|
494
638
|
busy: 0,
|
|
495
639
|
waiting_input: 0,
|
|
640
|
+
pending_auto_approval: 0,
|
|
496
641
|
total: sessions.length,
|
|
497
642
|
};
|
|
498
643
|
sessions.forEach(session => {
|
|
@@ -506,6 +651,9 @@ export class SessionManager extends EventEmitter {
|
|
|
506
651
|
case 'waiting_input':
|
|
507
652
|
counts.waiting_input++;
|
|
508
653
|
break;
|
|
654
|
+
case 'pending_auto_approval':
|
|
655
|
+
counts.pending_auto_approval++;
|
|
656
|
+
break;
|
|
509
657
|
}
|
|
510
658
|
});
|
|
511
659
|
return counts;
|
|
@@ -36,6 +36,8 @@ vi.mock('./configurationManager.js', () => ({
|
|
|
36
36
|
setWorktreeLastOpened: vi.fn(),
|
|
37
37
|
getWorktreeLastOpenedTime: vi.fn(),
|
|
38
38
|
getWorktreeLastOpened: vi.fn(() => ({})),
|
|
39
|
+
isAutoApprovalEnabled: vi.fn(() => false),
|
|
40
|
+
setAutoApprovalEnabled: vi.fn(),
|
|
39
41
|
},
|
|
40
42
|
}));
|
|
41
43
|
describe('SessionManager - State Persistence', () => {
|
|
@@ -22,6 +22,8 @@ vi.mock('./configurationManager.js', () => ({
|
|
|
22
22
|
setWorktreeLastOpened: vi.fn(),
|
|
23
23
|
getWorktreeLastOpenedTime: vi.fn(),
|
|
24
24
|
getWorktreeLastOpened: vi.fn(() => ({})),
|
|
25
|
+
isAutoApprovalEnabled: vi.fn(() => false),
|
|
26
|
+
setAutoApprovalEnabled: vi.fn(),
|
|
25
27
|
},
|
|
26
28
|
}));
|
|
27
29
|
// Mock Terminal
|
|
@@ -743,6 +745,7 @@ describe('SessionManager', () => {
|
|
|
743
745
|
idle: 1,
|
|
744
746
|
busy: 2,
|
|
745
747
|
waiting_input: 1,
|
|
748
|
+
pending_auto_approval: 0,
|
|
746
749
|
total: 4,
|
|
747
750
|
};
|
|
748
751
|
const formatted = SessionManager.formatSessionCounts(counts);
|
|
@@ -753,6 +756,7 @@ describe('SessionManager', () => {
|
|
|
753
756
|
idle: 2,
|
|
754
757
|
busy: 0,
|
|
755
758
|
waiting_input: 1,
|
|
759
|
+
pending_auto_approval: 0,
|
|
756
760
|
total: 3,
|
|
757
761
|
};
|
|
758
762
|
const formatted = SessionManager.formatSessionCounts(counts);
|
|
@@ -763,6 +767,7 @@ describe('SessionManager', () => {
|
|
|
763
767
|
idle: 0,
|
|
764
768
|
busy: 3,
|
|
765
769
|
waiting_input: 0,
|
|
770
|
+
pending_auto_approval: 0,
|
|
766
771
|
total: 3,
|
|
767
772
|
};
|
|
768
773
|
const formatted = SessionManager.formatSessionCounts(counts);
|
|
@@ -773,6 +778,7 @@ describe('SessionManager', () => {
|
|
|
773
778
|
idle: 0,
|
|
774
779
|
busy: 0,
|
|
775
780
|
waiting_input: 0,
|
|
781
|
+
pending_auto_approval: 0,
|
|
776
782
|
total: 0,
|
|
777
783
|
};
|
|
778
784
|
const formatted = SessionManager.formatSessionCounts(counts);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { IPty } from 'node-pty';
|
|
|
2
2
|
import type pkg from '@xterm/headless';
|
|
3
3
|
import { GitStatus } from '../utils/gitStatus.js';
|
|
4
4
|
export type Terminal = InstanceType<typeof pkg.Terminal>;
|
|
5
|
-
export type SessionState = 'idle' | 'busy' | 'waiting_input';
|
|
5
|
+
export type SessionState = 'idle' | 'busy' | 'waiting_input' | 'pending_auto_approval';
|
|
6
6
|
export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor' | 'github-copilot' | 'cline';
|
|
7
7
|
export interface Worktree {
|
|
8
8
|
path: string;
|
|
@@ -29,12 +29,20 @@ export interface Session {
|
|
|
29
29
|
devcontainerConfig: DevcontainerConfig | undefined;
|
|
30
30
|
pendingState: SessionState | undefined;
|
|
31
31
|
pendingStateStart: number | undefined;
|
|
32
|
+
autoApprovalFailed: boolean;
|
|
33
|
+
autoApprovalReason?: string;
|
|
34
|
+
autoApprovalAbortController?: AbortController;
|
|
35
|
+
}
|
|
36
|
+
export interface AutoApprovalResponse {
|
|
37
|
+
needsPermission: boolean;
|
|
38
|
+
reason?: string;
|
|
32
39
|
}
|
|
33
40
|
export interface SessionManager {
|
|
34
41
|
sessions: Map<string, Session>;
|
|
35
42
|
getSession(worktreePath: string): Session | undefined;
|
|
36
43
|
destroySession(worktreePath: string): void;
|
|
37
44
|
getAllSessions(): Session[];
|
|
45
|
+
cancelAutoApproval(worktreePath: string, reason?: string): void;
|
|
38
46
|
}
|
|
39
47
|
export interface ShortcutKey {
|
|
40
48
|
ctrl?: boolean;
|
|
@@ -55,6 +63,7 @@ export interface StatusHookConfig {
|
|
|
55
63
|
idle?: StatusHook;
|
|
56
64
|
busy?: StatusHook;
|
|
57
65
|
waiting_input?: StatusHook;
|
|
66
|
+
pending_auto_approval?: StatusHook;
|
|
58
67
|
}
|
|
59
68
|
export interface WorktreeHook {
|
|
60
69
|
command: string;
|
|
@@ -98,6 +107,10 @@ export interface ConfigurationData {
|
|
|
98
107
|
worktree?: WorktreeConfig;
|
|
99
108
|
command?: CommandConfig;
|
|
100
109
|
commandPresets?: CommandPresetsConfig;
|
|
110
|
+
autoApproval?: {
|
|
111
|
+
enabled: boolean;
|
|
112
|
+
customCommand?: string;
|
|
113
|
+
};
|
|
101
114
|
}
|
|
102
115
|
export interface GitProject {
|
|
103
116
|
name: string;
|
|
@@ -272,6 +272,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
272
272
|
pendingStateStart: undefined,
|
|
273
273
|
lastActivity: new Date(),
|
|
274
274
|
isActive: true,
|
|
275
|
+
autoApprovalFailed: false,
|
|
275
276
|
};
|
|
276
277
|
// Mock WorktreeService to return a worktree with the tmpDir path
|
|
277
278
|
vi.mocked(WorktreeService).mockImplementation(() => ({
|
|
@@ -292,6 +293,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
292
293
|
},
|
|
293
294
|
idle: { enabled: false, command: '' },
|
|
294
295
|
waiting_input: { enabled: false, command: '' },
|
|
296
|
+
pending_auto_approval: { enabled: false, command: '' },
|
|
295
297
|
});
|
|
296
298
|
try {
|
|
297
299
|
// Act - execute the hook and await it
|
|
@@ -325,6 +327,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
325
327
|
pendingStateStart: undefined,
|
|
326
328
|
lastActivity: new Date(),
|
|
327
329
|
isActive: true,
|
|
330
|
+
autoApprovalFailed: false,
|
|
328
331
|
};
|
|
329
332
|
// Mock WorktreeService to return a worktree with the tmpDir path
|
|
330
333
|
vi.mocked(WorktreeService).mockImplementation(() => ({
|
|
@@ -345,6 +348,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
345
348
|
},
|
|
346
349
|
idle: { enabled: false, command: '' },
|
|
347
350
|
waiting_input: { enabled: false, command: '' },
|
|
351
|
+
pending_auto_approval: { enabled: false, command: '' },
|
|
348
352
|
});
|
|
349
353
|
try {
|
|
350
354
|
// Act & Assert - should not throw even when hook fails
|
|
@@ -376,6 +380,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
376
380
|
pendingStateStart: undefined,
|
|
377
381
|
lastActivity: new Date(),
|
|
378
382
|
isActive: true,
|
|
383
|
+
autoApprovalFailed: false,
|
|
379
384
|
};
|
|
380
385
|
// Mock WorktreeService to return a worktree with the tmpDir path
|
|
381
386
|
vi.mocked(WorktreeService).mockImplementation(() => ({
|
|
@@ -396,6 +401,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
396
401
|
},
|
|
397
402
|
idle: { enabled: false, command: '' },
|
|
398
403
|
waiting_input: { enabled: false, command: '' },
|
|
404
|
+
pending_auto_approval: { enabled: false, command: '' },
|
|
399
405
|
});
|
|
400
406
|
try {
|
|
401
407
|
// Act
|
|
@@ -429,6 +435,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
429
435
|
pendingStateStart: undefined,
|
|
430
436
|
lastActivity: new Date(),
|
|
431
437
|
isActive: true,
|
|
438
|
+
autoApprovalFailed: false,
|
|
432
439
|
};
|
|
433
440
|
// Mock WorktreeService to fail with GitError
|
|
434
441
|
vi.mocked(WorktreeService).mockImplementation(() => ({
|
|
@@ -446,6 +453,7 @@ describe('hookExecutor Integration Tests', () => {
|
|
|
446
453
|
},
|
|
447
454
|
idle: { enabled: false, command: '' },
|
|
448
455
|
waiting_input: { enabled: false, command: '' },
|
|
456
|
+
pending_auto_approval: { enabled: false, command: '' },
|
|
449
457
|
});
|
|
450
458
|
try {
|
|
451
459
|
// Act - should not throw even when getWorktreesEffect fails
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,14 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Logger configuration with size management and log rotation
|
|
3
|
+
*/
|
|
4
|
+
interface LoggerConfig {
|
|
5
|
+
/** Maximum log file size in bytes (default: 5MB) */
|
|
6
|
+
maxSizeBytes: number;
|
|
7
|
+
/** Number of old logs to keep (default: 3) */
|
|
8
|
+
maxRotatedFiles: number;
|
|
9
|
+
/** Enable console output for errors (default: true) */
|
|
10
|
+
logErrorsToConsole: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* CLI-optimized logger with size management and rotation
|
|
14
|
+
*
|
|
15
|
+
* Features:
|
|
16
|
+
* - Automatic log rotation when file exceeds max size
|
|
17
|
+
* - Configurable retention (3 rotated files by default)
|
|
18
|
+
* - Atomic write operations to prevent corruption
|
|
19
|
+
* - Platform-aware log location (respects XDG_STATE_HOME on Linux)
|
|
20
|
+
* - Detailed timestamps and structured log lines
|
|
21
|
+
* - Sensitive information filtering on console output
|
|
22
|
+
*/
|
|
23
|
+
declare class Logger {
|
|
24
|
+
private readonly logFile;
|
|
25
|
+
private readonly config;
|
|
26
|
+
private writeQueue;
|
|
27
|
+
private isWriting;
|
|
28
|
+
constructor(config?: Partial<LoggerConfig>);
|
|
29
|
+
/**
|
|
30
|
+
* Resolve log file path following XDG Base Directory specification
|
|
31
|
+
* and respecting environment overrides for testing
|
|
32
|
+
*/
|
|
33
|
+
private resolveLogPath;
|
|
34
|
+
/**
|
|
35
|
+
* Initialize log file and ensure directory exists
|
|
36
|
+
*/
|
|
37
|
+
private initializeLogFile;
|
|
38
|
+
/**
|
|
39
|
+
* Check if log file exceeds size limit and rotate if needed
|
|
40
|
+
*/
|
|
41
|
+
private rotateLogIfNeeded;
|
|
42
|
+
/**
|
|
43
|
+
* Queue write operation to prevent concurrent writes
|
|
44
|
+
* This ensures log file integrity with atomic operations
|
|
45
|
+
*/
|
|
46
|
+
private queueWrite;
|
|
47
|
+
/**
|
|
48
|
+
* Process write queue sequentially to prevent concurrent writes
|
|
49
|
+
*/
|
|
50
|
+
private processQueue;
|
|
51
|
+
/**
|
|
52
|
+
* Write log entry with level and formatted message
|
|
53
|
+
*/
|
|
54
|
+
private writeLog;
|
|
55
|
+
/**
|
|
56
|
+
* Get the path to the current log file
|
|
57
|
+
* Useful for users to locate and inspect logs
|
|
58
|
+
*/
|
|
59
|
+
getLogPath(): string;
|
|
60
|
+
/**
|
|
61
|
+
* Log entry at LOG level (general information)
|
|
62
|
+
*/
|
|
63
|
+
log(...args: unknown[]): void;
|
|
64
|
+
/**
|
|
65
|
+
* Log entry at INFO level (significant events)
|
|
66
|
+
*/
|
|
67
|
+
info(...args: unknown[]): void;
|
|
68
|
+
/**
|
|
69
|
+
* Log entry at WARN level (potentially harmful situations)
|
|
70
|
+
*/
|
|
71
|
+
warn(...args: unknown[]): void;
|
|
72
|
+
/**
|
|
73
|
+
* Log entry at ERROR level (error conditions)
|
|
74
|
+
*/
|
|
75
|
+
error(...args: unknown[]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Log entry at DEBUG level (detailed diagnostic information)
|
|
78
|
+
* Only written to file, not to console
|
|
79
|
+
*/
|
|
80
|
+
debug(...args: unknown[]): void;
|
|
81
|
+
}
|
|
82
|
+
export declare const logger: Logger;
|
|
83
|
+
export {};
|