ccmanager 2.11.3 → 2.11.5

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.
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { GeminiStateDetector } from '../stateDetector.js';
3
+ import { createMockTerminal } from './testUtils.js';
4
+ describe('GeminiStateDetector', () => {
5
+ let detector;
6
+ let terminal;
7
+ beforeEach(() => {
8
+ detector = new GeminiStateDetector();
9
+ });
10
+ describe('detectState', () => {
11
+ it('should detect waiting_input when "Apply this change?" prompt is present', () => {
12
+ // Arrange
13
+ terminal = createMockTerminal([
14
+ 'Some output from Gemini',
15
+ '│ Apply this change?',
16
+ '│ > ',
17
+ ]);
18
+ // Act
19
+ const state = detector.detectState(terminal, 'idle');
20
+ // Assert
21
+ expect(state).toBe('waiting_input');
22
+ });
23
+ it('should detect waiting_input when "Allow execution?" prompt is present', () => {
24
+ // Arrange
25
+ terminal = createMockTerminal([
26
+ 'Command found: npm install',
27
+ '│ Allow execution?',
28
+ '│ > ',
29
+ ]);
30
+ // Act
31
+ const state = detector.detectState(terminal, 'idle');
32
+ // Assert
33
+ expect(state).toBe('waiting_input');
34
+ });
35
+ it('should detect waiting_input when "Do you want to proceed?" prompt is present', () => {
36
+ // Arrange
37
+ terminal = createMockTerminal([
38
+ 'Changes detected',
39
+ '│ Do you want to proceed?',
40
+ '│ > ',
41
+ ]);
42
+ // Act
43
+ const state = detector.detectState(terminal, 'idle');
44
+ // Assert
45
+ expect(state).toBe('waiting_input');
46
+ });
47
+ it('should detect waiting_input for multiline confirmation ending with "yes"', () => {
48
+ // Arrange
49
+ terminal = createMockTerminal([
50
+ 'Apply this change to the workspace?',
51
+ 'The operation will modify several files.',
52
+ ' yes',
53
+ ]);
54
+ // Act
55
+ const state = detector.detectState(terminal, 'idle');
56
+ // Assert
57
+ expect(state).toBe('waiting_input');
58
+ });
59
+ it('should detect busy when "esc to cancel" is present', () => {
60
+ // Arrange
61
+ terminal = createMockTerminal([
62
+ 'Processing your request...',
63
+ 'Press ESC to cancel',
64
+ ]);
65
+ // Act
66
+ const state = detector.detectState(terminal, 'idle');
67
+ // Assert
68
+ expect(state).toBe('busy');
69
+ });
70
+ it('should detect busy when "ESC to cancel" is present (case insensitive)', () => {
71
+ // Arrange
72
+ terminal = createMockTerminal([
73
+ 'Running command...',
74
+ 'Press Esc to cancel the operation',
75
+ ]);
76
+ // Act
77
+ const state = detector.detectState(terminal, 'idle');
78
+ // Assert
79
+ expect(state).toBe('busy');
80
+ });
81
+ it('should detect idle when no specific patterns are found', () => {
82
+ // Arrange
83
+ terminal = createMockTerminal([
84
+ 'Welcome to Gemini CLI',
85
+ 'Type your message below',
86
+ ]);
87
+ // Act
88
+ const state = detector.detectState(terminal, 'idle');
89
+ // Assert
90
+ expect(state).toBe('idle');
91
+ });
92
+ it('should handle empty terminal', () => {
93
+ // Arrange
94
+ terminal = createMockTerminal([]);
95
+ // Act
96
+ const state = detector.detectState(terminal, 'idle');
97
+ // Assert
98
+ expect(state).toBe('idle');
99
+ });
100
+ it('should prioritize waiting_input over busy state', () => {
101
+ // Arrange
102
+ terminal = createMockTerminal([
103
+ 'Press ESC to cancel',
104
+ '│ Apply this change?',
105
+ '│ > ',
106
+ ]);
107
+ // Act
108
+ const state = detector.detectState(terminal, 'idle');
109
+ // Assert
110
+ expect(state).toBe('waiting_input'); // waiting_input should take precedence
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { GitHubCopilotStateDetector } from '../stateDetector.js';
3
+ import { createMockTerminal } from './testUtils.js';
4
+ describe('GitHubCopilotStateDetector', () => {
5
+ let detector;
6
+ let terminal;
7
+ beforeEach(() => {
8
+ detector = new GitHubCopilotStateDetector();
9
+ });
10
+ it('detects waiting_input when prompt asks "Do you want" (case insensitive)', () => {
11
+ // Arrange
12
+ terminal = createMockTerminal([
13
+ 'Running GitHub Copilot CLI...',
14
+ '│ DO YOU WANT to run this command?',
15
+ '│ > ',
16
+ ]);
17
+ // Act
18
+ const state = detector.detectState(terminal, 'idle');
19
+ // Assert
20
+ expect(state).toBe('waiting_input');
21
+ });
22
+ it('detects busy when "Esc to cancel" is present', () => {
23
+ // Arrange
24
+ terminal = createMockTerminal([
25
+ 'Executing request...',
26
+ 'Press Esc to cancel',
27
+ ]);
28
+ // Act
29
+ const state = detector.detectState(terminal, 'idle');
30
+ // Assert
31
+ expect(state).toBe('busy');
32
+ });
33
+ it('prioritizes waiting_input over busy when both patterns exist', () => {
34
+ // Arrange
35
+ terminal = createMockTerminal([
36
+ 'Press Esc to cancel',
37
+ '│ Do you want to continue?',
38
+ ]);
39
+ // Act
40
+ const state = detector.detectState(terminal, 'idle');
41
+ // Assert
42
+ expect(state).toBe('waiting_input');
43
+ });
44
+ it('returns idle when no patterns match', () => {
45
+ // Arrange
46
+ terminal = createMockTerminal([
47
+ 'GitHub Copilot CLI ready.',
48
+ 'Type a command to begin.',
49
+ ]);
50
+ // Act
51
+ const state = detector.detectState(terminal, 'idle');
52
+ // Assert
53
+ expect(state).toBe('idle');
54
+ });
55
+ });
@@ -0,0 +1,7 @@
1
+ import type { Terminal } from '../../types/index.js';
2
+ /**
3
+ * Creates a mock Terminal object for testing state detectors.
4
+ * @param lines - Array of strings representing terminal output lines
5
+ * @returns Mock Terminal object with buffer interface
6
+ */
7
+ export declare const createMockTerminal: (lines: string[]) => Terminal;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Creates a mock Terminal object for testing state detectors.
3
+ * @param lines - Array of strings representing terminal output lines
4
+ * @returns Mock Terminal object with buffer interface
5
+ */
6
+ export const createMockTerminal = (lines) => {
7
+ const buffer = {
8
+ length: lines.length,
9
+ active: {
10
+ length: lines.length,
11
+ getLine: (index) => {
12
+ if (index >= 0 && index < lines.length) {
13
+ return {
14
+ translateToString: () => lines[index],
15
+ };
16
+ }
17
+ return null;
18
+ },
19
+ },
20
+ };
21
+ return { buffer };
22
+ };
@@ -45,6 +45,15 @@ export class ClaudeStateDetector extends BaseStateDetector {
45
45
  if (lowerContent.includes('ctrl+r to toggle')) {
46
46
  return currentState;
47
47
  }
48
+ // Check for interactive selection interface patterns
49
+ // These patterns indicate Claude is waiting for user interaction with navigation/selection UI
50
+ const hasInteractivePattern = lowerContent.includes('enter to select') ||
51
+ lowerContent.includes('tab/arrow keys to navigate') ||
52
+ lowerContent.includes('esc to cancel') ||
53
+ lowerContent.includes('ready to submit your answers?');
54
+ if (hasInteractivePattern) {
55
+ return 'waiting_input';
56
+ }
48
57
  // Check for waiting prompts with box character
49
58
  if (content.includes('│ Do you want') ||
50
59
  content.includes('│ Would you like')) {
@@ -74,6 +83,10 @@ export class GeminiStateDetector extends BaseStateDetector {
74
83
  content.includes('│ Do you want to proceed?')) {
75
84
  return 'waiting_input';
76
85
  }
86
+ // Check for multiline confirmation prompts ending with "yes"
87
+ if (/(allow execution|do you want to|apply this change)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
88
+ return 'waiting_input';
89
+ }
77
90
  // Check for busy state
78
91
  if (lowerContent.includes('esc to cancel')) {
79
92
  return 'busy';
@@ -92,6 +105,9 @@ export class CodexStateDetector extends BaseStateDetector {
92
105
  lowerContent.includes('yes (y)')) {
93
106
  return 'waiting_input';
94
107
  }
108
+ if (/(do you want|would you like)[\s\S]*?\n+[\s\S]*?\byes\b/.test(lowerContent)) {
109
+ return 'waiting_input';
110
+ }
95
111
  // Check for busy state
96
112
  if (/esc.*interrupt/i.test(lowerContent)) {
97
113
  return 'busy';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "2.11.3",
3
+ "version": "2.11.5",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",