ccmanager 3.2.4 → 3.2.6
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 +20 -0
- package/dist/services/__tests__/stateDetector.codex.test.js +49 -0
- package/dist/services/__tests__/stateDetector.gemini.test.js +58 -0
- package/dist/services/__tests__/stateDetector.github-copilot.test.js +30 -0
- package/dist/services/stateDetector.js +19 -6
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ https://github.com/user-attachments/assets/15914a88-e288-4ac9-94d5-8127f2e19dbf
|
|
|
20
20
|
- Configurable state detection strategies for different CLI tools
|
|
21
21
|
- Status change hooks for automation and notifications
|
|
22
22
|
- Devcontainer integration
|
|
23
|
+
- **Auto Approval (experimental)**: Automatically approve safe prompts using AI verification
|
|
23
24
|
|
|
24
25
|
## Why CCManager over Claude Squad?
|
|
25
26
|
|
|
@@ -249,6 +250,25 @@ CCManager can automatically generate worktree directory paths based on branch na
|
|
|
249
250
|
|
|
250
251
|
For detailed configuration and examples, see [docs/worktree-auto-directory.md](docs/worktree-auto-directory.md).
|
|
251
252
|
|
|
253
|
+
## Auto Approval (Experimental)
|
|
254
|
+
|
|
255
|
+
CCManager can automatically approve Claude Code prompts that don't require user permission, reducing manual intervention while maintaining safety for sensitive operations.
|
|
256
|
+
|
|
257
|
+
### Features
|
|
258
|
+
|
|
259
|
+
- **Automatic decision**: Uses Claude (Haiku) to analyze prompts and determine if they need manual approval
|
|
260
|
+
- **Custom commands**: Replace the default verifier with your own script or different AI model
|
|
261
|
+
- **Safe fallback**: Always defaults to manual approval on errors or timeouts
|
|
262
|
+
- **Interruptible**: Press any key to cancel auto-approval and review manually
|
|
263
|
+
|
|
264
|
+
### Quick Start
|
|
265
|
+
|
|
266
|
+
1. Navigate to **Configuration** → **Other & Experimental**
|
|
267
|
+
2. Enable **Auto Approval (experimental)**
|
|
268
|
+
3. (Optional) Configure a custom command for verification
|
|
269
|
+
|
|
270
|
+
For detailed configuration and usage, see [docs/auto-approval.md](docs/auto-approval.md).
|
|
271
|
+
|
|
252
272
|
## Devcontainer Integration
|
|
253
273
|
|
|
254
274
|
CCManager supports running AI assistant sessions inside devcontainers while keeping the manager itself on the host machine. This enables sandboxed development environments with restricted network access while maintaining host-level notifications and automation.
|
|
@@ -54,6 +54,28 @@ describe('CodexStateDetector', () => {
|
|
|
54
54
|
// Assert
|
|
55
55
|
expect(state).toBe('waiting_input');
|
|
56
56
|
});
|
|
57
|
+
it('should detect waiting_input state for "Press enter to confirm or esc to cancel" pattern', () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
terminal = createMockTerminal([
|
|
60
|
+
'Some output',
|
|
61
|
+
'Press enter to confirm or esc to cancel',
|
|
62
|
+
]);
|
|
63
|
+
// Act
|
|
64
|
+
const state = detector.detectState(terminal, 'idle');
|
|
65
|
+
// Assert
|
|
66
|
+
expect(state).toBe('waiting_input');
|
|
67
|
+
});
|
|
68
|
+
it('should prioritize "Press enter to confirm" over busy state with esc interrupt', () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
terminal = createMockTerminal([
|
|
71
|
+
'esc to interrupt',
|
|
72
|
+
'Press enter to confirm or esc to cancel',
|
|
73
|
+
]);
|
|
74
|
+
// Act
|
|
75
|
+
const state = detector.detectState(terminal, 'idle');
|
|
76
|
+
// Assert
|
|
77
|
+
expect(state).toBe('waiting_input');
|
|
78
|
+
});
|
|
57
79
|
it('should detect busy state for Esc to interrupt pattern', () => {
|
|
58
80
|
// Arrange
|
|
59
81
|
terminal = createMockTerminal([
|
|
@@ -94,4 +116,31 @@ describe('CodexStateDetector', () => {
|
|
|
94
116
|
// Assert
|
|
95
117
|
expect(state).toBe('waiting_input');
|
|
96
118
|
});
|
|
119
|
+
it('should detect waiting_input state for "Confirm with ... Enter" pattern', () => {
|
|
120
|
+
// Arrange
|
|
121
|
+
terminal = createMockTerminal(['Some output', 'Confirm with Y Enter']);
|
|
122
|
+
// Act
|
|
123
|
+
const state = detector.detectState(terminal, 'idle');
|
|
124
|
+
// Assert
|
|
125
|
+
expect(state).toBe('waiting_input');
|
|
126
|
+
});
|
|
127
|
+
it('should detect waiting_input for "Confirm with" pattern with longer text', () => {
|
|
128
|
+
// Arrange
|
|
129
|
+
terminal = createMockTerminal([
|
|
130
|
+
'Some output',
|
|
131
|
+
'Confirm with Shift + Y Enter',
|
|
132
|
+
]);
|
|
133
|
+
// Act
|
|
134
|
+
const state = detector.detectState(terminal, 'idle');
|
|
135
|
+
// Assert
|
|
136
|
+
expect(state).toBe('waiting_input');
|
|
137
|
+
});
|
|
138
|
+
it('should prioritize "Confirm with ... Enter" over busy state', () => {
|
|
139
|
+
// Arrange
|
|
140
|
+
terminal = createMockTerminal(['Esc to interrupt', 'Confirm with Y Enter']);
|
|
141
|
+
// Act
|
|
142
|
+
const state = detector.detectState(terminal, 'idle');
|
|
143
|
+
// Assert
|
|
144
|
+
expect(state).toBe('waiting_input');
|
|
145
|
+
});
|
|
97
146
|
});
|
|
@@ -20,6 +20,18 @@ describe('GeminiStateDetector', () => {
|
|
|
20
20
|
// Assert
|
|
21
21
|
expect(state).toBe('waiting_input');
|
|
22
22
|
});
|
|
23
|
+
it('should detect waiting_input when "Apply this change" prompt is present (without ?)', () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
terminal = createMockTerminal([
|
|
26
|
+
'Some output from Gemini',
|
|
27
|
+
'│ Apply this change',
|
|
28
|
+
'│ > ',
|
|
29
|
+
]);
|
|
30
|
+
// Act
|
|
31
|
+
const state = detector.detectState(terminal, 'idle');
|
|
32
|
+
// Assert
|
|
33
|
+
expect(state).toBe('waiting_input');
|
|
34
|
+
});
|
|
23
35
|
it('should detect waiting_input when "Allow execution?" prompt is present', () => {
|
|
24
36
|
// Arrange
|
|
25
37
|
terminal = createMockTerminal([
|
|
@@ -32,6 +44,18 @@ describe('GeminiStateDetector', () => {
|
|
|
32
44
|
// Assert
|
|
33
45
|
expect(state).toBe('waiting_input');
|
|
34
46
|
});
|
|
47
|
+
it('should detect waiting_input when "Allow execution" prompt is present (without ?)', () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
terminal = createMockTerminal([
|
|
50
|
+
'Command found: npm install',
|
|
51
|
+
'│ Allow execution',
|
|
52
|
+
'│ > ',
|
|
53
|
+
]);
|
|
54
|
+
// Act
|
|
55
|
+
const state = detector.detectState(terminal, 'idle');
|
|
56
|
+
// Assert
|
|
57
|
+
expect(state).toBe('waiting_input');
|
|
58
|
+
});
|
|
35
59
|
it('should detect waiting_input when "Do you want to proceed?" prompt is present', () => {
|
|
36
60
|
// Arrange
|
|
37
61
|
terminal = createMockTerminal([
|
|
@@ -44,6 +68,40 @@ describe('GeminiStateDetector', () => {
|
|
|
44
68
|
// Assert
|
|
45
69
|
expect(state).toBe('waiting_input');
|
|
46
70
|
});
|
|
71
|
+
it('should detect waiting_input when "Do you want to proceed" prompt is present (without ?)', () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
terminal = createMockTerminal([
|
|
74
|
+
'Changes detected',
|
|
75
|
+
'│ Do you want to proceed',
|
|
76
|
+
'│ > ',
|
|
77
|
+
]);
|
|
78
|
+
// Act
|
|
79
|
+
const state = detector.detectState(terminal, 'idle');
|
|
80
|
+
// Assert
|
|
81
|
+
expect(state).toBe('waiting_input');
|
|
82
|
+
});
|
|
83
|
+
it('should detect waiting_input when "Waiting for user confirmation..." is present', () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
terminal = createMockTerminal([
|
|
86
|
+
'Processing...',
|
|
87
|
+
'Waiting for user confirmation...',
|
|
88
|
+
]);
|
|
89
|
+
// Act
|
|
90
|
+
const state = detector.detectState(terminal, 'idle');
|
|
91
|
+
// Assert
|
|
92
|
+
expect(state).toBe('waiting_input');
|
|
93
|
+
});
|
|
94
|
+
it('should prioritize "Waiting for user confirmation" over busy state', () => {
|
|
95
|
+
// Arrange
|
|
96
|
+
terminal = createMockTerminal([
|
|
97
|
+
'Press ESC to cancel',
|
|
98
|
+
'Waiting for user confirmation...',
|
|
99
|
+
]);
|
|
100
|
+
// Act
|
|
101
|
+
const state = detector.detectState(terminal, 'idle');
|
|
102
|
+
// Assert
|
|
103
|
+
expect(state).toBe('waiting_input');
|
|
104
|
+
});
|
|
47
105
|
it('should detect waiting_input for multiline confirmation ending with "yes"', () => {
|
|
48
106
|
// Arrange
|
|
49
107
|
terminal = createMockTerminal([
|
|
@@ -19,6 +19,36 @@ describe('GitHubCopilotStateDetector', () => {
|
|
|
19
19
|
// Assert
|
|
20
20
|
expect(state).toBe('waiting_input');
|
|
21
21
|
});
|
|
22
|
+
it('detects waiting_input when "Confirm with ... Enter" pattern is present', () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
terminal = createMockTerminal(['Some output', 'Confirm with Y Enter']);
|
|
25
|
+
// Act
|
|
26
|
+
const state = detector.detectState(terminal, 'idle');
|
|
27
|
+
// Assert
|
|
28
|
+
expect(state).toBe('waiting_input');
|
|
29
|
+
});
|
|
30
|
+
it('detects waiting_input for "Confirm with" pattern with longer text', () => {
|
|
31
|
+
// Arrange
|
|
32
|
+
terminal = createMockTerminal([
|
|
33
|
+
'Some output',
|
|
34
|
+
'Confirm with Shift + Y Enter',
|
|
35
|
+
]);
|
|
36
|
+
// Act
|
|
37
|
+
const state = detector.detectState(terminal, 'idle');
|
|
38
|
+
// Assert
|
|
39
|
+
expect(state).toBe('waiting_input');
|
|
40
|
+
});
|
|
41
|
+
it('prioritizes "Confirm with ... Enter" over busy state', () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
terminal = createMockTerminal([
|
|
44
|
+
'Press Esc to cancel',
|
|
45
|
+
'Confirm with Y Enter',
|
|
46
|
+
]);
|
|
47
|
+
// Act
|
|
48
|
+
const state = detector.detectState(terminal, 'idle');
|
|
49
|
+
// Assert
|
|
50
|
+
expect(state).toBe('waiting_input');
|
|
51
|
+
});
|
|
22
52
|
it('detects busy when "Esc to cancel" is present', () => {
|
|
23
53
|
// Arrange
|
|
24
54
|
terminal = createMockTerminal([
|
|
@@ -63,10 +63,14 @@ export class GeminiStateDetector extends BaseStateDetector {
|
|
|
63
63
|
detectState(terminal, _currentState) {
|
|
64
64
|
const content = this.getTerminalContent(terminal);
|
|
65
65
|
const lowerContent = content.toLowerCase();
|
|
66
|
+
// Check for explicit user confirmation message - highest priority
|
|
67
|
+
if (lowerContent.includes('waiting for user confirmation')) {
|
|
68
|
+
return 'waiting_input';
|
|
69
|
+
}
|
|
66
70
|
// Check for waiting prompts with box character
|
|
67
|
-
if (content.includes('│ Apply this change
|
|
68
|
-
content.includes('│ Allow execution
|
|
69
|
-
content.includes('│ Do you want to proceed
|
|
71
|
+
if (content.includes('│ Apply this change') ||
|
|
72
|
+
content.includes('│ Allow execution') ||
|
|
73
|
+
content.includes('│ Do you want to proceed')) {
|
|
70
74
|
return 'waiting_input';
|
|
71
75
|
}
|
|
72
76
|
// Check for multiline confirmation prompts ending with "yes"
|
|
@@ -85,6 +89,11 @@ export class CodexStateDetector extends BaseStateDetector {
|
|
|
85
89
|
detectState(terminal, _currentState) {
|
|
86
90
|
const content = this.getTerminalContent(terminal);
|
|
87
91
|
const lowerContent = content.toLowerCase();
|
|
92
|
+
// Check for confirmation prompt patterns - highest priority
|
|
93
|
+
if (lowerContent.includes('press enter to confirm or esc to cancel') ||
|
|
94
|
+
/confirm with .+ enter/i.test(content)) {
|
|
95
|
+
return 'waiting_input';
|
|
96
|
+
}
|
|
88
97
|
// Check for waiting prompts
|
|
89
98
|
if (lowerContent.includes('allow command?') ||
|
|
90
99
|
lowerContent.includes('[y/n]') ||
|
|
@@ -124,15 +133,19 @@ export class GitHubCopilotStateDetector extends BaseStateDetector {
|
|
|
124
133
|
detectState(terminal, _currentState) {
|
|
125
134
|
const content = this.getTerminalContent(terminal);
|
|
126
135
|
const lowerContent = content.toLowerCase();
|
|
127
|
-
//
|
|
136
|
+
// Check for confirmation prompt pattern - highest priority
|
|
137
|
+
if (/confirm with .+ enter/i.test(content)) {
|
|
138
|
+
return 'waiting_input';
|
|
139
|
+
}
|
|
140
|
+
// Waiting prompt has priority 2
|
|
128
141
|
if (lowerContent.includes('│ do you want')) {
|
|
129
142
|
return 'waiting_input';
|
|
130
143
|
}
|
|
131
|
-
// Busy state detection has priority
|
|
144
|
+
// Busy state detection has priority 3
|
|
132
145
|
if (lowerContent.includes('esc to cancel')) {
|
|
133
146
|
return 'busy';
|
|
134
147
|
}
|
|
135
|
-
// Otherwise idle as priority
|
|
148
|
+
// Otherwise idle as priority 4
|
|
136
149
|
return 'idle';
|
|
137
150
|
}
|
|
138
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.6",
|
|
4
4
|
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kodai Kabasawa",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"bin"
|
|
42
42
|
],
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@kodaikabasawa/ccmanager-darwin-arm64": "3.2.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "3.2.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "3.2.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "3.2.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "3.2.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "3.2.6",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.2.6",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.2.6",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.2.6",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.2.6"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|