ccmanager 3.3.2 → 3.5.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/README.md +11 -5
- package/dist/components/App.js +17 -3
- package/dist/components/App.test.js +5 -5
- package/dist/components/Configuration.d.ts +2 -0
- package/dist/components/Configuration.js +6 -2
- package/dist/components/ConfigureCommand.js +34 -11
- package/dist/components/ConfigureOther.js +18 -4
- package/dist/components/ConfigureOther.test.js +48 -12
- package/dist/components/ConfigureShortcuts.js +27 -85
- package/dist/components/ConfigureStatusHooks.js +19 -4
- package/dist/components/ConfigureStatusHooks.test.js +46 -12
- package/dist/components/ConfigureWorktree.js +18 -4
- package/dist/components/ConfigureWorktreeHooks.js +19 -4
- package/dist/components/ConfigureWorktreeHooks.test.js +49 -14
- package/dist/components/Menu.js +72 -14
- package/dist/components/Menu.recent-projects.test.js +2 -0
- package/dist/components/Menu.test.js +2 -0
- package/dist/components/NewWorktree.js +2 -2
- package/dist/components/NewWorktree.test.js +6 -6
- package/dist/components/PresetSelector.js +2 -2
- package/dist/constants/statusIcons.d.ts +4 -1
- package/dist/constants/statusIcons.js +10 -1
- package/dist/constants/statusIcons.test.js +42 -0
- package/dist/contexts/ConfigEditorContext.d.ts +21 -0
- package/dist/contexts/ConfigEditorContext.js +25 -0
- package/dist/services/autoApprovalVerifier.js +3 -3
- package/dist/services/autoApprovalVerifier.test.js +2 -2
- package/dist/services/config/configEditor.d.ts +46 -0
- package/dist/services/{configurationManager.effect.test.js → config/configEditor.effect.test.js} +46 -49
- package/dist/services/config/configEditor.js +101 -0
- package/dist/services/{configurationManager.selectPresetOnStart.test.js → config/configEditor.selectPresetOnStart.test.js} +27 -19
- package/dist/services/config/configEditor.test.d.ts +1 -0
- package/dist/services/{configurationManager.test.js → config/configEditor.test.js} +60 -132
- package/dist/services/config/configReader.d.ts +28 -0
- package/dist/services/config/configReader.js +95 -0
- package/dist/services/config/configReader.multiProject.test.d.ts +1 -0
- package/dist/services/config/configReader.multiProject.test.js +136 -0
- package/dist/services/config/globalConfigManager.d.ts +30 -0
- package/dist/services/config/globalConfigManager.js +216 -0
- package/dist/services/config/index.d.ts +13 -0
- package/dist/services/config/index.js +13 -0
- package/dist/services/config/projectConfigManager.d.ts +41 -0
- package/dist/services/config/projectConfigManager.js +181 -0
- package/dist/services/config/projectConfigManager.test.d.ts +1 -0
- package/dist/services/config/projectConfigManager.test.js +105 -0
- package/dist/services/config/testUtils.d.ts +81 -0
- package/dist/services/config/testUtils.js +351 -0
- package/dist/services/sessionManager.autoApproval.test.js +9 -6
- package/dist/services/sessionManager.d.ts +2 -0
- package/dist/services/sessionManager.effect.test.js +27 -18
- package/dist/services/sessionManager.js +43 -40
- package/dist/services/sessionManager.statePersistence.test.js +5 -4
- package/dist/services/sessionManager.test.js +71 -49
- package/dist/services/shortcutManager.d.ts +0 -1
- package/dist/services/shortcutManager.js +5 -16
- package/dist/services/shortcutManager.test.js +2 -2
- package/dist/services/stateDetector/base.d.ts +1 -0
- package/dist/services/stateDetector/claude.d.ts +1 -0
- package/dist/services/stateDetector/claude.js +8 -0
- package/dist/services/stateDetector/claude.test.js +102 -0
- package/dist/services/stateDetector/cline.d.ts +1 -0
- package/dist/services/stateDetector/cline.js +3 -0
- package/dist/services/stateDetector/codex.d.ts +1 -0
- package/dist/services/stateDetector/codex.js +3 -0
- package/dist/services/stateDetector/cursor.d.ts +1 -0
- package/dist/services/stateDetector/cursor.js +3 -0
- package/dist/services/stateDetector/gemini.d.ts +1 -0
- package/dist/services/stateDetector/gemini.js +3 -0
- package/dist/services/stateDetector/github-copilot.d.ts +1 -0
- package/dist/services/stateDetector/github-copilot.js +3 -0
- package/dist/services/stateDetector/opencode.d.ts +1 -0
- package/dist/services/stateDetector/opencode.js +3 -0
- package/dist/services/stateDetector/types.d.ts +1 -0
- package/dist/services/worktreeService.d.ts +12 -0
- package/dist/services/worktreeService.js +24 -4
- package/dist/services/worktreeService.sort.test.js +105 -109
- package/dist/services/worktreeService.test.js +5 -5
- package/dist/types/index.d.ts +47 -7
- package/dist/utils/gitUtils.d.ts +8 -0
- package/dist/utils/gitUtils.js +32 -0
- package/dist/utils/hookExecutor.js +2 -2
- package/dist/utils/hookExecutor.test.js +13 -12
- package/dist/utils/mutex.d.ts +1 -0
- package/dist/utils/mutex.js +1 -0
- package/dist/utils/worktreeUtils.js +3 -2
- package/dist/utils/worktreeUtils.test.js +2 -1
- package/package.json +7 -7
- package/dist/services/configurationManager.d.ts +0 -121
- package/dist/services/configurationManager.js +0 -597
- /package/dist/{services/configurationManager.effect.test.d.ts → constants/statusIcons.test.d.ts} +0 -0
- /package/dist/services/{configurationManager.selectPresetOnStart.test.d.ts → config/configEditor.effect.test.d.ts} +0 -0
- /package/dist/services/{configurationManager.test.d.ts → config/configEditor.selectPresetOnStart.test.d.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { configReader } from './config/configReader.js';
|
|
2
2
|
export class ShortcutManager {
|
|
3
3
|
constructor() {
|
|
4
4
|
Object.defineProperty(this, "reservedKeys", {
|
|
@@ -46,19 +46,8 @@ export class ShortcutManager {
|
|
|
46
46
|
reserved.alt === shortcut.alt &&
|
|
47
47
|
reserved.shift === shortcut.shift);
|
|
48
48
|
}
|
|
49
|
-
saveShortcuts(shortcuts) {
|
|
50
|
-
// Validate all shortcuts
|
|
51
|
-
const currentShortcuts = configurationManager.getShortcuts();
|
|
52
|
-
const validated = {
|
|
53
|
-
returnToMenu: this.validateShortcut(shortcuts.returnToMenu) ||
|
|
54
|
-
currentShortcuts.returnToMenu,
|
|
55
|
-
cancel: this.validateShortcut(shortcuts.cancel) || currentShortcuts.cancel,
|
|
56
|
-
};
|
|
57
|
-
configurationManager.setShortcuts(validated);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
49
|
getShortcuts() {
|
|
61
|
-
return
|
|
50
|
+
return configReader.getShortcuts();
|
|
62
51
|
}
|
|
63
52
|
getRawShortcutCodes(shortcut) {
|
|
64
53
|
const codes = new Set();
|
|
@@ -106,7 +95,7 @@ export class ShortcutManager {
|
|
|
106
95
|
return Array.from(codes);
|
|
107
96
|
}
|
|
108
97
|
matchesShortcut(shortcutName, input, key) {
|
|
109
|
-
const shortcuts =
|
|
98
|
+
const shortcuts = configReader.getShortcuts();
|
|
110
99
|
const shortcut = shortcuts[shortcutName];
|
|
111
100
|
if (!shortcut)
|
|
112
101
|
return false;
|
|
@@ -125,7 +114,7 @@ export class ShortcutManager {
|
|
|
125
114
|
return input.toLowerCase() === shortcut.key.toLowerCase();
|
|
126
115
|
}
|
|
127
116
|
getShortcutDisplay(shortcutName) {
|
|
128
|
-
const shortcuts =
|
|
117
|
+
const shortcuts = configReader.getShortcuts();
|
|
129
118
|
const shortcut = shortcuts[shortcutName];
|
|
130
119
|
if (!shortcut)
|
|
131
120
|
return '';
|
|
@@ -161,7 +150,7 @@ export class ShortcutManager {
|
|
|
161
150
|
return null;
|
|
162
151
|
}
|
|
163
152
|
matchesRawInput(shortcutName, input) {
|
|
164
|
-
const shortcuts =
|
|
153
|
+
const shortcuts = configReader.getShortcuts();
|
|
165
154
|
const shortcut = shortcuts[shortcutName];
|
|
166
155
|
if (!shortcut)
|
|
167
156
|
return false;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { shortcutManager } from './shortcutManager.js';
|
|
3
|
-
import {
|
|
3
|
+
import { configReader } from './config/configReader.js';
|
|
4
4
|
describe('shortcutManager.matchesRawInput', () => {
|
|
5
5
|
const shortcuts = {
|
|
6
6
|
returnToMenu: { ctrl: true, key: 'e', alt: false, shift: false },
|
|
7
7
|
cancel: { ctrl: true, key: 'c', alt: false, shift: false },
|
|
8
8
|
};
|
|
9
9
|
beforeEach(() => {
|
|
10
|
-
vi.spyOn(
|
|
10
|
+
vi.spyOn(configReader, 'getShortcuts').mockReturnValue(shortcuts);
|
|
11
11
|
});
|
|
12
12
|
afterEach(() => {
|
|
13
13
|
vi.restoreAllMocks();
|
|
@@ -4,4 +4,5 @@ export declare abstract class BaseStateDetector implements StateDetector {
|
|
|
4
4
|
abstract detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
5
5
|
protected getTerminalLines(terminal: Terminal, maxLines?: number): string[];
|
|
6
6
|
protected getTerminalContent(terminal: Terminal, maxLines?: number): string;
|
|
7
|
+
abstract detectBackgroundTask(terminal: Terminal): boolean;
|
|
7
8
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class ClaudeStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -24,4 +24,12 @@ export class ClaudeStateDetector extends BaseStateDetector {
|
|
|
24
24
|
// Otherwise idle
|
|
25
25
|
return 'idle';
|
|
26
26
|
}
|
|
27
|
+
detectBackgroundTask(terminal) {
|
|
28
|
+
const lines = this.getTerminalLines(terminal, 3);
|
|
29
|
+
const content = lines.join('\n').toLowerCase();
|
|
30
|
+
// Detect background task patterns:
|
|
31
|
+
// - "N background task(s)" in status bar
|
|
32
|
+
// - "(running)" in status bar for active background commands
|
|
33
|
+
return content.includes('background task') || content.includes('(running)');
|
|
34
|
+
}
|
|
27
35
|
}
|
|
@@ -222,4 +222,106 @@ describe('ClaudeStateDetector', () => {
|
|
|
222
222
|
expect(state).toBe('waiting_input');
|
|
223
223
|
});
|
|
224
224
|
});
|
|
225
|
+
describe('detectBackgroundTask', () => {
|
|
226
|
+
it('should detect background task when pattern is in last 3 lines (status bar)', () => {
|
|
227
|
+
// Arrange
|
|
228
|
+
terminal = createMockTerminal([
|
|
229
|
+
'Previous conversation content',
|
|
230
|
+
'More content',
|
|
231
|
+
'> Some command output',
|
|
232
|
+
'1 background task | api-call',
|
|
233
|
+
]);
|
|
234
|
+
// Act
|
|
235
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
236
|
+
// Assert
|
|
237
|
+
expect(hasBackgroundTask).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
it('should detect background task with plural "background tasks"', () => {
|
|
240
|
+
// Arrange
|
|
241
|
+
terminal = createMockTerminal([
|
|
242
|
+
'Some output',
|
|
243
|
+
'More output',
|
|
244
|
+
'2 background tasks running',
|
|
245
|
+
]);
|
|
246
|
+
// Act
|
|
247
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
248
|
+
// Assert
|
|
249
|
+
expect(hasBackgroundTask).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
it('should detect background task case-insensitively', () => {
|
|
252
|
+
// Arrange
|
|
253
|
+
terminal = createMockTerminal([
|
|
254
|
+
'Output line 1',
|
|
255
|
+
'Output line 2',
|
|
256
|
+
'1 BACKGROUND TASK running',
|
|
257
|
+
]);
|
|
258
|
+
// Act
|
|
259
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
260
|
+
// Assert
|
|
261
|
+
expect(hasBackgroundTask).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
it('should return false when no background task pattern in last 3 lines', () => {
|
|
264
|
+
// Arrange
|
|
265
|
+
terminal = createMockTerminal([
|
|
266
|
+
'Command completed successfully',
|
|
267
|
+
'Ready for next command',
|
|
268
|
+
'> ',
|
|
269
|
+
]);
|
|
270
|
+
// Act
|
|
271
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
272
|
+
// Assert
|
|
273
|
+
expect(hasBackgroundTask).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
it('should not detect background task when pattern is in conversation content (not status bar)', () => {
|
|
276
|
+
// Arrange - "background task" mentioned earlier in conversation, but not in last 3 lines
|
|
277
|
+
terminal = createMockTerminal([
|
|
278
|
+
'User: Tell me about background task handling',
|
|
279
|
+
'Assistant: Background task detection works by...',
|
|
280
|
+
'The pattern "background task" appears in text but...',
|
|
281
|
+
'This is the status bar area',
|
|
282
|
+
'> idle',
|
|
283
|
+
'Ready',
|
|
284
|
+
]);
|
|
285
|
+
// Act
|
|
286
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
287
|
+
// Assert - should only check last 3 lines, not the conversation content
|
|
288
|
+
expect(hasBackgroundTask).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
it('should handle empty terminal', () => {
|
|
291
|
+
// Arrange
|
|
292
|
+
terminal = createMockTerminal([]);
|
|
293
|
+
// Act
|
|
294
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
295
|
+
// Assert
|
|
296
|
+
expect(hasBackgroundTask).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
it('should handle terminal with fewer than 3 lines', () => {
|
|
299
|
+
// Arrange
|
|
300
|
+
terminal = createMockTerminal(['1 background task']);
|
|
301
|
+
// Act
|
|
302
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
303
|
+
// Assert
|
|
304
|
+
expect(hasBackgroundTask).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
it('should detect "(running)" status bar indicator', () => {
|
|
307
|
+
// Arrange
|
|
308
|
+
terminal = createMockTerminal([
|
|
309
|
+
'Some conversation output',
|
|
310
|
+
'More output',
|
|
311
|
+
'bypass permissions on - uv run pytest tests/integration/e2e/tes... (running)',
|
|
312
|
+
]);
|
|
313
|
+
// Act
|
|
314
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
315
|
+
// Assert
|
|
316
|
+
expect(hasBackgroundTask).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
it('should detect "(running)" case-insensitively', () => {
|
|
319
|
+
// Arrange
|
|
320
|
+
terminal = createMockTerminal(['Some output', 'command name (RUNNING)']);
|
|
321
|
+
// Act
|
|
322
|
+
const hasBackgroundTask = detector.detectBackgroundTask(terminal);
|
|
323
|
+
// Assert
|
|
324
|
+
expect(hasBackgroundTask).toBe(true);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
225
327
|
});
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class ClineStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class CodexStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class CursorStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class GeminiStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class GitHubCopilotStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -2,4 +2,5 @@ import { SessionState, Terminal } from '../../types/index.js';
|
|
|
2
2
|
import { BaseStateDetector } from './base.js';
|
|
3
3
|
export declare class OpenCodeStateDetector extends BaseStateDetector {
|
|
4
4
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
5
|
+
detectBackgroundTask(_terminal: Terminal): boolean;
|
|
5
6
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
2
|
import { Worktree } from '../types/index.js';
|
|
3
3
|
import { GitError, FileSystemError } from '../types/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Get all worktree last opened timestamps
|
|
6
|
+
*/
|
|
7
|
+
export declare function getWorktreeLastOpened(): Record<string, number>;
|
|
8
|
+
/**
|
|
9
|
+
* Set the last opened timestamp for a worktree
|
|
10
|
+
*/
|
|
11
|
+
export declare function setWorktreeLastOpened(worktreePath: string, timestamp: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get the last opened timestamp for a specific worktree
|
|
14
|
+
*/
|
|
15
|
+
export declare function getWorktreeLastOpenedTime(worktreePath: string): number | undefined;
|
|
4
16
|
/**
|
|
5
17
|
* WorktreeService - Git worktree management with Effect-based error handling
|
|
6
18
|
*
|
|
@@ -7,8 +7,28 @@ import { GitError, FileSystemError } from '../types/errors.js';
|
|
|
7
7
|
import { setWorktreeParentBranch } from '../utils/worktreeConfig.js';
|
|
8
8
|
import { getClaudeProjectsDir, pathToClaudeProjectName, } from '../utils/claudeDir.js';
|
|
9
9
|
import { executeWorktreePostCreationHook } from '../utils/hookExecutor.js';
|
|
10
|
-
import {
|
|
10
|
+
import { configReader } from './config/configReader.js';
|
|
11
11
|
const CLAUDE_DIR = '.claude';
|
|
12
|
+
// Module-level state for worktree last opened tracking (runtime state, not persisted)
|
|
13
|
+
const worktreeLastOpened = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Get all worktree last opened timestamps
|
|
16
|
+
*/
|
|
17
|
+
export function getWorktreeLastOpened() {
|
|
18
|
+
return Object.fromEntries(worktreeLastOpened);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Set the last opened timestamp for a worktree
|
|
22
|
+
*/
|
|
23
|
+
export function setWorktreeLastOpened(worktreePath, timestamp) {
|
|
24
|
+
worktreeLastOpened.set(worktreePath, timestamp);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the last opened timestamp for a specific worktree
|
|
28
|
+
*/
|
|
29
|
+
export function getWorktreeLastOpenedTime(worktreePath) {
|
|
30
|
+
return worktreeLastOpened.get(worktreePath);
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* WorktreeService - Git worktree management with Effect-based error handling
|
|
14
34
|
*
|
|
@@ -641,8 +661,8 @@ export class WorktreeService {
|
|
|
641
661
|
if (sortByLastSession) {
|
|
642
662
|
worktrees.sort((a, b) => {
|
|
643
663
|
// Get last opened timestamps for both worktrees
|
|
644
|
-
const timeA =
|
|
645
|
-
const timeB =
|
|
664
|
+
const timeA = getWorktreeLastOpenedTime(a.path);
|
|
665
|
+
const timeB = getWorktreeLastOpenedTime(b.path);
|
|
646
666
|
// If both timestamps are undefined, preserve original order
|
|
647
667
|
if (timeA === undefined && timeB === undefined) {
|
|
648
668
|
return 0;
|
|
@@ -804,7 +824,7 @@ export class WorktreeService {
|
|
|
804
824
|
});
|
|
805
825
|
}
|
|
806
826
|
// Execute post-creation hook if configured
|
|
807
|
-
const worktreeHooks =
|
|
827
|
+
const worktreeHooks = configReader.getWorktreeHooks();
|
|
808
828
|
if (worktreeHooks.post_creation?.enabled &&
|
|
809
829
|
worktreeHooks.post_creation?.command) {
|
|
810
830
|
const newWorktree = {
|