ccmanager 3.2.7 → 3.2.8
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 +0 -18
- package/dist/services/sessionManager.autoApproval.test.js +1 -1
- package/dist/services/sessionManager.js +1 -1
- package/dist/services/stateDetector/base.d.ts +7 -0
- package/dist/services/stateDetector/base.js +21 -0
- package/dist/services/stateDetector/claude.d.ts +5 -0
- package/dist/services/stateDetector/claude.js +26 -0
- package/dist/services/{__tests__/stateDetector.claude.test.js → stateDetector/claude.test.js} +44 -1
- package/dist/services/stateDetector/cline.d.ts +5 -0
- package/dist/services/stateDetector/cline.js +24 -0
- package/dist/services/{__tests__/stateDetector.cline.test.js → stateDetector/cline.test.js} +1 -1
- package/dist/services/stateDetector/codex.d.ts +5 -0
- package/dist/services/stateDetector/codex.js +27 -0
- package/dist/services/{__tests__/stateDetector.codex.test.js → stateDetector/codex.test.js} +1 -1
- package/dist/services/stateDetector/cursor.d.ts +5 -0
- package/dist/services/stateDetector/cursor.js +19 -0
- package/dist/services/{__tests__/stateDetector.cursor.test.js → stateDetector/cursor.test.js} +1 -1
- package/dist/services/stateDetector/gemini.d.ts +5 -0
- package/dist/services/stateDetector/gemini.js +28 -0
- package/dist/services/{__tests__/stateDetector.gemini.test.js → stateDetector/gemini.test.js} +1 -1
- package/dist/services/stateDetector/github-copilot.d.ts +5 -0
- package/dist/services/stateDetector/github-copilot.js +21 -0
- package/dist/services/{__tests__/stateDetector.github-copilot.test.js → stateDetector/github-copilot.test.js} +1 -1
- package/dist/services/stateDetector/index.d.ts +3 -0
- package/dist/services/stateDetector/index.js +24 -0
- package/dist/services/stateDetector/types.d.ts +4 -0
- package/dist/services/stateDetector/types.js +1 -0
- package/package.json +6 -6
- package/dist/services/stateDetector.d.ts +0 -28
- package/dist/services/stateDetector.js +0 -174
- /package/dist/services/{__tests__/stateDetector.claude.test.d.ts → stateDetector/claude.test.d.ts} +0 -0
- /package/dist/services/{__tests__/stateDetector.cline.test.d.ts → stateDetector/cline.test.d.ts} +0 -0
- /package/dist/services/{__tests__/stateDetector.codex.test.d.ts → stateDetector/codex.test.d.ts} +0 -0
- /package/dist/services/{__tests__/stateDetector.cursor.test.d.ts → stateDetector/cursor.test.d.ts} +0 -0
- /package/dist/services/{__tests__/stateDetector.gemini.test.d.ts → stateDetector/gemini.test.d.ts} +0 -0
- /package/dist/services/{__tests__/stateDetector.github-copilot.test.d.ts → stateDetector/github-copilot.test.d.ts} +0 -0
- /package/dist/services/{__tests__ → stateDetector}/testUtils.d.ts +0 -0
- /package/dist/services/{__tests__ → stateDetector}/testUtils.js +0 -0
package/README.md
CHANGED
|
@@ -44,30 +44,12 @@ Claude Squad doesn't show session states in its menu, making it hard to know whi
|
|
|
44
44
|
### 🎯 Simple and intuitive interface
|
|
45
45
|
Following Claude Code's philosophy, CCManager keeps things minimal and intuitive. The interface is so simple you'll understand it in seconds - no manual needed.
|
|
46
46
|
|
|
47
|
-
## Requirements
|
|
48
|
-
|
|
49
|
-
- **Node.js 22 or later** is required to run CCManager.
|
|
50
|
-
|
|
51
47
|
## Install
|
|
52
48
|
|
|
53
49
|
```bash
|
|
54
50
|
npm install -g ccmanager
|
|
55
51
|
```
|
|
56
52
|
|
|
57
|
-
### Using mise
|
|
58
|
-
|
|
59
|
-
If you use [mise](https://mise.jdx.dev/) as a version manager, you can install CCManager with:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
mise install npm:ccmanager && mise use -g npm:ccmanager
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
To run CCManager with Node.js 22:
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
mise exec node@22 -- ccmanager
|
|
69
|
-
```
|
|
70
|
-
|
|
71
53
|
### Local Development
|
|
72
54
|
|
|
73
55
|
```bash
|
|
@@ -14,7 +14,7 @@ vi.mock('./bunTerminal.js', () => ({
|
|
|
14
14
|
return null;
|
|
15
15
|
}),
|
|
16
16
|
}));
|
|
17
|
-
vi.mock('./stateDetector.js', () => ({
|
|
17
|
+
vi.mock('./stateDetector/index.js', () => ({
|
|
18
18
|
createStateDetector: () => ({ detectState: detectStateMock }),
|
|
19
19
|
}));
|
|
20
20
|
vi.mock('./configurationManager.js', () => ({
|
|
@@ -5,7 +5,7 @@ import { exec } from 'child_process';
|
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { configurationManager } from './configurationManager.js';
|
|
7
7
|
import { executeStatusHook } from '../utils/hookExecutor.js';
|
|
8
|
-
import { createStateDetector } from './stateDetector.js';
|
|
8
|
+
import { createStateDetector } from './stateDetector/index.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';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SessionState, Terminal } from '../../types/index.js';
|
|
2
|
+
import { StateDetector } from './types.js';
|
|
3
|
+
export declare abstract class BaseStateDetector implements StateDetector {
|
|
4
|
+
abstract detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
5
|
+
protected getTerminalLines(terminal: Terminal, maxLines?: number): string[];
|
|
6
|
+
protected getTerminalContent(terminal: Terminal, maxLines?: number): string;
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class BaseStateDetector {
|
|
2
|
+
getTerminalLines(terminal, maxLines = 30) {
|
|
3
|
+
const buffer = terminal.buffer.active;
|
|
4
|
+
const lines = [];
|
|
5
|
+
// Start from the bottom and work our way up
|
|
6
|
+
for (let i = buffer.length - 1; i >= 0 && lines.length < maxLines; i--) {
|
|
7
|
+
const line = buffer.getLine(i);
|
|
8
|
+
if (line) {
|
|
9
|
+
const text = line.translateToString(true);
|
|
10
|
+
// Skip empty lines at the bottom
|
|
11
|
+
if (lines.length > 0 || text.trim() !== '') {
|
|
12
|
+
lines.unshift(text);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return lines;
|
|
17
|
+
}
|
|
18
|
+
getTerminalContent(terminal, maxLines = 30) {
|
|
19
|
+
return this.getTerminalLines(terminal, maxLines).join('\n');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
export class ClaudeStateDetector extends BaseStateDetector {
|
|
3
|
+
detectState(terminal, currentState) {
|
|
4
|
+
const content = this.getTerminalContent(terminal);
|
|
5
|
+
const lowerContent = content.toLowerCase();
|
|
6
|
+
// Check for ctrl+r toggle prompt - maintain current state
|
|
7
|
+
if (lowerContent.includes('ctrl+r to toggle')) {
|
|
8
|
+
return currentState;
|
|
9
|
+
}
|
|
10
|
+
// Check for "Do you want" or "Would you like" pattern with options
|
|
11
|
+
// Handles both simple ("Do you want...\nYes") and complex (numbered options) formats
|
|
12
|
+
if (/(?:do you want|would you like).+\n+[\s\S]*?(?:yes|❯)/.test(lowerContent)) {
|
|
13
|
+
return 'waiting_input';
|
|
14
|
+
}
|
|
15
|
+
// Check for "esc to cancel" - indicates waiting for user input
|
|
16
|
+
if (lowerContent.includes('esc to cancel')) {
|
|
17
|
+
return 'waiting_input';
|
|
18
|
+
}
|
|
19
|
+
// Check for busy state
|
|
20
|
+
if (lowerContent.includes('esc to interrupt')) {
|
|
21
|
+
return 'busy';
|
|
22
|
+
}
|
|
23
|
+
// Otherwise idle
|
|
24
|
+
return 'idle';
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/services/{__tests__/stateDetector.claude.test.js → stateDetector/claude.test.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { ClaudeStateDetector } from '
|
|
2
|
+
import { ClaudeStateDetector } from './claude.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('ClaudeStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -167,5 +167,48 @@ describe('ClaudeStateDetector', () => {
|
|
|
167
167
|
// Assert
|
|
168
168
|
expect(state).toBe('waiting_input');
|
|
169
169
|
});
|
|
170
|
+
it('should detect waiting_input when "Yes" has characters before it (e.g., "❯ 1. Yes")', () => {
|
|
171
|
+
// Arrange
|
|
172
|
+
terminal = createMockTerminal([
|
|
173
|
+
'Do you want to continue?',
|
|
174
|
+
'❯ 1. Yes',
|
|
175
|
+
' 2. No',
|
|
176
|
+
]);
|
|
177
|
+
// Act
|
|
178
|
+
const state = detector.detectState(terminal, 'idle');
|
|
179
|
+
// Assert
|
|
180
|
+
expect(state).toBe('waiting_input');
|
|
181
|
+
});
|
|
182
|
+
it('should detect waiting_input when "esc to cancel" is present', () => {
|
|
183
|
+
// Arrange
|
|
184
|
+
terminal = createMockTerminal([
|
|
185
|
+
'Enter your message:',
|
|
186
|
+
'Press esc to cancel',
|
|
187
|
+
]);
|
|
188
|
+
// Act
|
|
189
|
+
const state = detector.detectState(terminal, 'idle');
|
|
190
|
+
// Assert
|
|
191
|
+
expect(state).toBe('waiting_input');
|
|
192
|
+
});
|
|
193
|
+
it('should detect waiting_input when "esc to cancel" is present (case insensitive)', () => {
|
|
194
|
+
// Arrange
|
|
195
|
+
terminal = createMockTerminal(['Waiting for input', 'ESC TO CANCEL']);
|
|
196
|
+
// Act
|
|
197
|
+
const state = detector.detectState(terminal, 'idle');
|
|
198
|
+
// Assert
|
|
199
|
+
expect(state).toBe('waiting_input');
|
|
200
|
+
});
|
|
201
|
+
it('should prioritize "esc to cancel" over "esc to interrupt" when both present', () => {
|
|
202
|
+
// Arrange
|
|
203
|
+
terminal = createMockTerminal([
|
|
204
|
+
'Press esc to interrupt',
|
|
205
|
+
'Some input prompt',
|
|
206
|
+
'Press esc to cancel',
|
|
207
|
+
]);
|
|
208
|
+
// Act
|
|
209
|
+
const state = detector.detectState(terminal, 'idle');
|
|
210
|
+
// Assert
|
|
211
|
+
expect(state).toBe('waiting_input');
|
|
212
|
+
});
|
|
170
213
|
});
|
|
171
214
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
// https://github.com/cline/cline/blob/580db36476b6b52def03c8aeda325aae1c817cde/cli/pkg/cli/task/input_handler.go
|
|
3
|
+
export class ClineStateDetector extends BaseStateDetector {
|
|
4
|
+
detectState(terminal, _currentState) {
|
|
5
|
+
const content = this.getTerminalContent(terminal);
|
|
6
|
+
const lowerContent = content.toLowerCase();
|
|
7
|
+
// Check for waiting prompts with tool permission - Priority 1
|
|
8
|
+
// Pattern: [\[act|plan\] mode].*?\n.*yes (when mode indicator present)
|
|
9
|
+
// Or simply: let cline use this tool (distinctive text)
|
|
10
|
+
if (/\[(act|plan) mode\].*?\n.*yes/i.test(lowerContent) ||
|
|
11
|
+
/let cline use this tool/i.test(lowerContent)) {
|
|
12
|
+
return 'waiting_input';
|
|
13
|
+
}
|
|
14
|
+
// Check for idle state - Priority 2
|
|
15
|
+
// Pattern: [\[act|plan\] mode].*Cline is ready for your message... (when mode indicator present)
|
|
16
|
+
// Or simply: cline is ready for your message (distinctive text)
|
|
17
|
+
if (/\[(act|plan) mode\].*cline is ready for your message/i.test(lowerContent) ||
|
|
18
|
+
/cline is ready for your message/i.test(lowerContent)) {
|
|
19
|
+
return 'idle';
|
|
20
|
+
}
|
|
21
|
+
// Otherwise busy - Priority 3
|
|
22
|
+
return 'busy';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { ClineStateDetector } from '
|
|
2
|
+
import { ClineStateDetector } from './cline.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('ClineStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
export class CodexStateDetector extends BaseStateDetector {
|
|
3
|
+
detectState(terminal, _currentState) {
|
|
4
|
+
const content = this.getTerminalContent(terminal);
|
|
5
|
+
const lowerContent = content.toLowerCase();
|
|
6
|
+
// Check for confirmation prompt patterns - highest priority
|
|
7
|
+
if (lowerContent.includes('press enter to confirm or esc to cancel') ||
|
|
8
|
+
/confirm with .+ enter/i.test(content)) {
|
|
9
|
+
return 'waiting_input';
|
|
10
|
+
}
|
|
11
|
+
// Check for waiting prompts
|
|
12
|
+
if (lowerContent.includes('allow command?') ||
|
|
13
|
+
lowerContent.includes('[y/n]') ||
|
|
14
|
+
lowerContent.includes('yes (y)')) {
|
|
15
|
+
return 'waiting_input';
|
|
16
|
+
}
|
|
17
|
+
if (/(do you want|would you like)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
|
|
18
|
+
return 'waiting_input';
|
|
19
|
+
}
|
|
20
|
+
// Check for busy state
|
|
21
|
+
if (/esc.*interrupt/i.test(lowerContent)) {
|
|
22
|
+
return 'busy';
|
|
23
|
+
}
|
|
24
|
+
// Otherwise idle
|
|
25
|
+
return 'idle';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { CodexStateDetector } from '
|
|
2
|
+
import { CodexStateDetector } from './codex.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('CodexStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
export class CursorStateDetector extends BaseStateDetector {
|
|
3
|
+
detectState(terminal, _currentState) {
|
|
4
|
+
const content = this.getTerminalContent(terminal);
|
|
5
|
+
const lowerContent = content.toLowerCase();
|
|
6
|
+
// Check for waiting prompts - Priority 1
|
|
7
|
+
if (lowerContent.includes('(y) (enter)') ||
|
|
8
|
+
lowerContent.includes('keep (n)') ||
|
|
9
|
+
/auto .* \(shift\+tab\)/.test(lowerContent)) {
|
|
10
|
+
return 'waiting_input';
|
|
11
|
+
}
|
|
12
|
+
// Check for busy state - Priority 2
|
|
13
|
+
if (lowerContent.includes('ctrl+c to stop')) {
|
|
14
|
+
return 'busy';
|
|
15
|
+
}
|
|
16
|
+
// Otherwise idle - Priority 3
|
|
17
|
+
return 'idle';
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/services/{__tests__/stateDetector.cursor.test.js → stateDetector/cursor.test.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { CursorStateDetector } from '
|
|
2
|
+
import { CursorStateDetector } from './cursor.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('CursorStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
// https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
|
|
3
|
+
export class GeminiStateDetector extends BaseStateDetector {
|
|
4
|
+
detectState(terminal, _currentState) {
|
|
5
|
+
const content = this.getTerminalContent(terminal);
|
|
6
|
+
const lowerContent = content.toLowerCase();
|
|
7
|
+
// Check for explicit user confirmation message - highest priority
|
|
8
|
+
if (lowerContent.includes('waiting for user confirmation')) {
|
|
9
|
+
return 'waiting_input';
|
|
10
|
+
}
|
|
11
|
+
// Check for waiting prompts with box character
|
|
12
|
+
if (content.includes('│ Apply this change') ||
|
|
13
|
+
content.includes('│ Allow execution') ||
|
|
14
|
+
content.includes('│ Do you want to proceed')) {
|
|
15
|
+
return 'waiting_input';
|
|
16
|
+
}
|
|
17
|
+
// Check for multiline confirmation prompts ending with "yes"
|
|
18
|
+
if (/(allow execution|do you want to|apply this change)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
|
|
19
|
+
return 'waiting_input';
|
|
20
|
+
}
|
|
21
|
+
// Check for busy state
|
|
22
|
+
if (lowerContent.includes('esc to cancel')) {
|
|
23
|
+
return 'busy';
|
|
24
|
+
}
|
|
25
|
+
// Otherwise idle
|
|
26
|
+
return 'idle';
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/services/{__tests__/stateDetector.gemini.test.js → stateDetector/gemini.test.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { GeminiStateDetector } from '
|
|
2
|
+
import { GeminiStateDetector } from './gemini.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('GeminiStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
export class GitHubCopilotStateDetector extends BaseStateDetector {
|
|
3
|
+
detectState(terminal, _currentState) {
|
|
4
|
+
const content = this.getTerminalContent(terminal);
|
|
5
|
+
const lowerContent = content.toLowerCase();
|
|
6
|
+
// Check for confirmation prompt pattern - highest priority
|
|
7
|
+
if (/confirm with .+ enter/i.test(content)) {
|
|
8
|
+
return 'waiting_input';
|
|
9
|
+
}
|
|
10
|
+
// Waiting prompt has priority 2
|
|
11
|
+
if (lowerContent.includes('│ do you want')) {
|
|
12
|
+
return 'waiting_input';
|
|
13
|
+
}
|
|
14
|
+
// Busy state detection has priority 3
|
|
15
|
+
if (lowerContent.includes('esc to cancel')) {
|
|
16
|
+
return 'busy';
|
|
17
|
+
}
|
|
18
|
+
// Otherwise idle as priority 4
|
|
19
|
+
return 'idle';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { GitHubCopilotStateDetector } from '
|
|
2
|
+
import { GitHubCopilotStateDetector } from './github-copilot.js';
|
|
3
3
|
import { createMockTerminal } from './testUtils.js';
|
|
4
4
|
describe('GitHubCopilotStateDetector', () => {
|
|
5
5
|
let detector;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ClaudeStateDetector } from './claude.js';
|
|
2
|
+
import { GeminiStateDetector } from './gemini.js';
|
|
3
|
+
import { CodexStateDetector } from './codex.js';
|
|
4
|
+
import { CursorStateDetector } from './cursor.js';
|
|
5
|
+
import { GitHubCopilotStateDetector } from './github-copilot.js';
|
|
6
|
+
import { ClineStateDetector } from './cline.js';
|
|
7
|
+
export function createStateDetector(strategy = 'claude') {
|
|
8
|
+
switch (strategy) {
|
|
9
|
+
case 'claude':
|
|
10
|
+
return new ClaudeStateDetector();
|
|
11
|
+
case 'gemini':
|
|
12
|
+
return new GeminiStateDetector();
|
|
13
|
+
case 'codex':
|
|
14
|
+
return new CodexStateDetector();
|
|
15
|
+
case 'cursor':
|
|
16
|
+
return new CursorStateDetector();
|
|
17
|
+
case 'github-copilot':
|
|
18
|
+
return new GitHubCopilotStateDetector();
|
|
19
|
+
case 'cline':
|
|
20
|
+
return new ClineStateDetector();
|
|
21
|
+
default:
|
|
22
|
+
return new ClaudeStateDetector();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.8",
|
|
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.8",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.2.8",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.2.8",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.2.8",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.2.8"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { SessionState, Terminal, StateDetectionStrategy } from '../types/index.js';
|
|
2
|
-
export interface StateDetector {
|
|
3
|
-
detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
4
|
-
}
|
|
5
|
-
export declare function createStateDetector(strategy?: StateDetectionStrategy): StateDetector;
|
|
6
|
-
export declare abstract class BaseStateDetector implements StateDetector {
|
|
7
|
-
abstract detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
8
|
-
protected getTerminalLines(terminal: Terminal, maxLines?: number): string[];
|
|
9
|
-
protected getTerminalContent(terminal: Terminal, maxLines?: number): string;
|
|
10
|
-
}
|
|
11
|
-
export declare class ClaudeStateDetector extends BaseStateDetector {
|
|
12
|
-
detectState(terminal: Terminal, currentState: SessionState): SessionState;
|
|
13
|
-
}
|
|
14
|
-
export declare class GeminiStateDetector extends BaseStateDetector {
|
|
15
|
-
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
16
|
-
}
|
|
17
|
-
export declare class CodexStateDetector extends BaseStateDetector {
|
|
18
|
-
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
19
|
-
}
|
|
20
|
-
export declare class CursorStateDetector extends BaseStateDetector {
|
|
21
|
-
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
22
|
-
}
|
|
23
|
-
export declare class GitHubCopilotStateDetector extends BaseStateDetector {
|
|
24
|
-
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
25
|
-
}
|
|
26
|
-
export declare class ClineStateDetector extends BaseStateDetector {
|
|
27
|
-
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
28
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
export function createStateDetector(strategy = 'claude') {
|
|
2
|
-
switch (strategy) {
|
|
3
|
-
case 'claude':
|
|
4
|
-
return new ClaudeStateDetector();
|
|
5
|
-
case 'gemini':
|
|
6
|
-
return new GeminiStateDetector();
|
|
7
|
-
case 'codex':
|
|
8
|
-
return new CodexStateDetector();
|
|
9
|
-
case 'cursor':
|
|
10
|
-
return new CursorStateDetector();
|
|
11
|
-
case 'github-copilot':
|
|
12
|
-
return new GitHubCopilotStateDetector();
|
|
13
|
-
case 'cline':
|
|
14
|
-
return new ClineStateDetector();
|
|
15
|
-
default:
|
|
16
|
-
return new ClaudeStateDetector();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export class BaseStateDetector {
|
|
20
|
-
getTerminalLines(terminal, maxLines = 30) {
|
|
21
|
-
const buffer = terminal.buffer.active;
|
|
22
|
-
const lines = [];
|
|
23
|
-
// Start from the bottom and work our way up
|
|
24
|
-
for (let i = buffer.length - 1; i >= 0 && lines.length < maxLines; i--) {
|
|
25
|
-
const line = buffer.getLine(i);
|
|
26
|
-
if (line) {
|
|
27
|
-
const text = line.translateToString(true);
|
|
28
|
-
// Skip empty lines at the bottom
|
|
29
|
-
if (lines.length > 0 || text.trim() !== '') {
|
|
30
|
-
lines.unshift(text);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return lines;
|
|
35
|
-
}
|
|
36
|
-
getTerminalContent(terminal, maxLines = 30) {
|
|
37
|
-
return this.getTerminalLines(terminal, maxLines).join('\n');
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export class ClaudeStateDetector extends BaseStateDetector {
|
|
41
|
-
detectState(terminal, currentState) {
|
|
42
|
-
const content = this.getTerminalContent(terminal);
|
|
43
|
-
const lowerContent = content.toLowerCase();
|
|
44
|
-
// Check for ctrl+r toggle prompt - maintain current state
|
|
45
|
-
if (lowerContent.includes('ctrl+r to toggle')) {
|
|
46
|
-
return currentState;
|
|
47
|
-
}
|
|
48
|
-
// Check for "Do you want" or "Would you like" pattern with options
|
|
49
|
-
// Handles both simple ("Do you want...\nYes") and complex (numbered options) formats
|
|
50
|
-
if (/(?:do you want|would you like).+\n+[\s\S]*?(?:yes|❯)/.test(lowerContent)) {
|
|
51
|
-
return 'waiting_input';
|
|
52
|
-
}
|
|
53
|
-
// Check for busy state
|
|
54
|
-
if (lowerContent.includes('esc to interrupt')) {
|
|
55
|
-
return 'busy';
|
|
56
|
-
}
|
|
57
|
-
// Otherwise idle
|
|
58
|
-
return 'idle';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
|
|
62
|
-
export class GeminiStateDetector extends BaseStateDetector {
|
|
63
|
-
detectState(terminal, _currentState) {
|
|
64
|
-
const content = this.getTerminalContent(terminal);
|
|
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
|
-
}
|
|
70
|
-
// Check for waiting prompts with box character
|
|
71
|
-
if (content.includes('│ Apply this change') ||
|
|
72
|
-
content.includes('│ Allow execution') ||
|
|
73
|
-
content.includes('│ Do you want to proceed')) {
|
|
74
|
-
return 'waiting_input';
|
|
75
|
-
}
|
|
76
|
-
// Check for multiline confirmation prompts ending with "yes"
|
|
77
|
-
if (/(allow execution|do you want to|apply this change)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
|
|
78
|
-
return 'waiting_input';
|
|
79
|
-
}
|
|
80
|
-
// Check for busy state
|
|
81
|
-
if (lowerContent.includes('esc to cancel')) {
|
|
82
|
-
return 'busy';
|
|
83
|
-
}
|
|
84
|
-
// Otherwise idle
|
|
85
|
-
return 'idle';
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export class CodexStateDetector extends BaseStateDetector {
|
|
89
|
-
detectState(terminal, _currentState) {
|
|
90
|
-
const content = this.getTerminalContent(terminal);
|
|
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
|
-
}
|
|
97
|
-
// Check for waiting prompts
|
|
98
|
-
if (lowerContent.includes('allow command?') ||
|
|
99
|
-
lowerContent.includes('[y/n]') ||
|
|
100
|
-
lowerContent.includes('yes (y)')) {
|
|
101
|
-
return 'waiting_input';
|
|
102
|
-
}
|
|
103
|
-
if (/(do you want|would you like)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
|
|
104
|
-
return 'waiting_input';
|
|
105
|
-
}
|
|
106
|
-
// Check for busy state
|
|
107
|
-
if (/esc.*interrupt/i.test(lowerContent)) {
|
|
108
|
-
return 'busy';
|
|
109
|
-
}
|
|
110
|
-
// Otherwise idle
|
|
111
|
-
return 'idle';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
export class CursorStateDetector extends BaseStateDetector {
|
|
115
|
-
detectState(terminal, _currentState) {
|
|
116
|
-
const content = this.getTerminalContent(terminal);
|
|
117
|
-
const lowerContent = content.toLowerCase();
|
|
118
|
-
// Check for waiting prompts - Priority 1
|
|
119
|
-
if (lowerContent.includes('(y) (enter)') ||
|
|
120
|
-
lowerContent.includes('keep (n)') ||
|
|
121
|
-
/auto .* \(shift\+tab\)/.test(lowerContent)) {
|
|
122
|
-
return 'waiting_input';
|
|
123
|
-
}
|
|
124
|
-
// Check for busy state - Priority 2
|
|
125
|
-
if (lowerContent.includes('ctrl+c to stop')) {
|
|
126
|
-
return 'busy';
|
|
127
|
-
}
|
|
128
|
-
// Otherwise idle - Priority 3
|
|
129
|
-
return 'idle';
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
export class GitHubCopilotStateDetector extends BaseStateDetector {
|
|
133
|
-
detectState(terminal, _currentState) {
|
|
134
|
-
const content = this.getTerminalContent(terminal);
|
|
135
|
-
const lowerContent = content.toLowerCase();
|
|
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
|
|
141
|
-
if (lowerContent.includes('│ do you want')) {
|
|
142
|
-
return 'waiting_input';
|
|
143
|
-
}
|
|
144
|
-
// Busy state detection has priority 3
|
|
145
|
-
if (lowerContent.includes('esc to cancel')) {
|
|
146
|
-
return 'busy';
|
|
147
|
-
}
|
|
148
|
-
// Otherwise idle as priority 4
|
|
149
|
-
return 'idle';
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// https://github.com/cline/cline/blob/580db36476b6b52def03c8aeda325aae1c817cde/cli/pkg/cli/task/input_handler.go
|
|
153
|
-
export class ClineStateDetector extends BaseStateDetector {
|
|
154
|
-
detectState(terminal, _currentState) {
|
|
155
|
-
const content = this.getTerminalContent(terminal);
|
|
156
|
-
const lowerContent = content.toLowerCase();
|
|
157
|
-
// Check for waiting prompts with tool permission - Priority 1
|
|
158
|
-
// Pattern: [\[act|plan\] mode].*?\n.*yes (when mode indicator present)
|
|
159
|
-
// Or simply: let cline use this tool (distinctive text)
|
|
160
|
-
if (/\[(act|plan) mode\].*?\n.*yes/i.test(lowerContent) ||
|
|
161
|
-
/let cline use this tool/i.test(lowerContent)) {
|
|
162
|
-
return 'waiting_input';
|
|
163
|
-
}
|
|
164
|
-
// Check for idle state - Priority 2
|
|
165
|
-
// Pattern: [\[act|plan\] mode].*Cline is ready for your message... (when mode indicator present)
|
|
166
|
-
// Or simply: cline is ready for your message (distinctive text)
|
|
167
|
-
if (/\[(act|plan) mode\].*cline is ready for your message/i.test(lowerContent) ||
|
|
168
|
-
/cline is ready for your message/i.test(lowerContent)) {
|
|
169
|
-
return 'idle';
|
|
170
|
-
}
|
|
171
|
-
// Otherwise busy - Priority 3
|
|
172
|
-
return 'busy';
|
|
173
|
-
}
|
|
174
|
-
}
|
/package/dist/services/{__tests__/stateDetector.claude.test.d.ts → stateDetector/claude.test.d.ts}
RENAMED
|
File without changes
|
/package/dist/services/{__tests__/stateDetector.cline.test.d.ts → stateDetector/cline.test.d.ts}
RENAMED
|
File without changes
|
/package/dist/services/{__tests__/stateDetector.codex.test.d.ts → stateDetector/codex.test.d.ts}
RENAMED
|
File without changes
|
/package/dist/services/{__tests__/stateDetector.cursor.test.d.ts → stateDetector/cursor.test.d.ts}
RENAMED
|
File without changes
|
/package/dist/services/{__tests__/stateDetector.gemini.test.d.ts → stateDetector/gemini.test.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|