ccmanager 2.6.1 → 2.7.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.
@@ -14,6 +14,10 @@ const createStrategyItems = () => {
14
14
  gemini: { label: 'Gemini', value: 'gemini' },
15
15
  codex: { label: 'Codex', value: 'codex' },
16
16
  cursor: { label: 'Cursor Agent', value: 'cursor' },
17
+ 'github-copilot': {
18
+ label: 'GitHub Copilot CLI',
19
+ value: 'github-copilot',
20
+ },
17
21
  };
18
22
  return Object.values(strategies);
19
23
  };
@@ -28,6 +32,8 @@ const formatDetectionStrategy = (strategy) => {
28
32
  return 'Codex';
29
33
  case 'cursor':
30
34
  return 'Cursor';
35
+ case 'github-copilot':
36
+ return 'GitHub Copilot CLI';
31
37
  default:
32
38
  return 'Claude';
33
39
  }
@@ -20,3 +20,6 @@ export declare class CodexStateDetector extends BaseStateDetector {
20
20
  export declare class CursorStateDetector extends BaseStateDetector {
21
21
  detectState(terminal: Terminal, _currentState: SessionState): SessionState;
22
22
  }
23
+ export declare class GitHubCopilotStateDetector extends BaseStateDetector {
24
+ detectState(terminal: Terminal, _currentState: SessionState): SessionState;
25
+ }
@@ -8,6 +8,8 @@ export function createStateDetector(strategy = 'claude') {
8
8
  return new CodexStateDetector();
9
9
  case 'cursor':
10
10
  return new CursorStateDetector();
11
+ case 'github-copilot':
12
+ return new GitHubCopilotStateDetector();
11
13
  default:
12
14
  return new ClaudeStateDetector();
13
15
  }
@@ -109,3 +111,19 @@ export class CursorStateDetector extends BaseStateDetector {
109
111
  return 'idle';
110
112
  }
111
113
  }
114
+ export class GitHubCopilotStateDetector extends BaseStateDetector {
115
+ detectState(terminal, _currentState) {
116
+ const content = this.getTerminalContent(terminal);
117
+ const lowerContent = content.toLowerCase();
118
+ // Waiting prompt has priority 1
119
+ if (lowerContent.includes('│ do you want')) {
120
+ return 'waiting_input';
121
+ }
122
+ // Busy state detection has priority 2
123
+ if (lowerContent.includes('esc to cancel')) {
124
+ return 'busy';
125
+ }
126
+ // Otherwise idle as priority 3
127
+ return 'idle';
128
+ }
129
+ }
@@ -1,10 +1,9 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, CursorStateDetector, } from './stateDetector.js';
3
- describe('ClaudeStateDetector', () => {
4
- let detector;
5
- let terminal;
6
- const createMockTerminal = (lines) => {
7
- const buffer = {
2
+ import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, CursorStateDetector, GitHubCopilotStateDetector, } from './stateDetector.js';
3
+ const createMockTerminal = (lines) => {
4
+ const buffer = {
5
+ length: lines.length,
6
+ active: {
8
7
  length: lines.length,
9
8
  getLine: (index) => {
10
9
  if (index >= 0 && index < lines.length) {
@@ -14,13 +13,13 @@ describe('ClaudeStateDetector', () => {
14
13
  }
15
14
  return null;
16
15
  },
17
- };
18
- return {
19
- buffer: {
20
- active: buffer,
21
- },
22
- };
16
+ },
23
17
  };
18
+ return { buffer };
19
+ };
20
+ describe('ClaudeStateDetector', () => {
21
+ let detector;
22
+ let terminal;
24
23
  beforeEach(() => {
25
24
  detector = new ClaudeStateDetector();
26
25
  });
@@ -159,24 +158,6 @@ describe('ClaudeStateDetector', () => {
159
158
  describe('GeminiStateDetector', () => {
160
159
  let detector;
161
160
  let terminal;
162
- const createMockTerminal = (lines) => {
163
- const buffer = {
164
- length: lines.length,
165
- getLine: (index) => {
166
- if (index >= 0 && index < lines.length) {
167
- return {
168
- translateToString: () => lines[index],
169
- };
170
- }
171
- return null;
172
- },
173
- };
174
- return {
175
- buffer: {
176
- active: buffer,
177
- },
178
- };
179
- };
180
161
  beforeEach(() => {
181
162
  detector = new GeminiStateDetector();
182
163
  });
@@ -275,23 +256,6 @@ describe('GeminiStateDetector', () => {
275
256
  describe('CodexStateDetector', () => {
276
257
  let detector;
277
258
  let terminal;
278
- const createMockTerminal = (lines) => {
279
- const buffer = {
280
- length: lines.length,
281
- active: {
282
- length: lines.length,
283
- getLine: (index) => {
284
- if (index >= 0 && index < lines.length) {
285
- return {
286
- translateToString: () => lines[index],
287
- };
288
- }
289
- return null;
290
- },
291
- },
292
- };
293
- return { buffer };
294
- };
295
259
  beforeEach(() => {
296
260
  detector = new CodexStateDetector();
297
261
  });
@@ -366,23 +330,6 @@ describe('CodexStateDetector', () => {
366
330
  describe('CursorStateDetector', () => {
367
331
  let detector;
368
332
  let terminal;
369
- const createMockTerminal = (lines) => {
370
- const buffer = {
371
- length: lines.length,
372
- active: {
373
- length: lines.length,
374
- getLine: (index) => {
375
- if (index >= 0 && index < lines.length) {
376
- return {
377
- translateToString: () => lines[index],
378
- };
379
- }
380
- return null;
381
- },
382
- },
383
- };
384
- return { buffer };
385
- };
386
333
  beforeEach(() => {
387
334
  detector = new CursorStateDetector();
388
335
  });
@@ -507,3 +454,55 @@ describe('CursorStateDetector', () => {
507
454
  expect(state).toBe('idle');
508
455
  });
509
456
  });
457
+ describe('GitHubCopilotStateDetector', () => {
458
+ let detector;
459
+ let terminal;
460
+ beforeEach(() => {
461
+ detector = new GitHubCopilotStateDetector();
462
+ });
463
+ it('detects waiting_input when prompt asks "Do you want" (case insensitive)', () => {
464
+ // Arrange
465
+ terminal = createMockTerminal([
466
+ 'Running GitHub Copilot CLI...',
467
+ '│ DO YOU WANT to run this command?',
468
+ '│ > ',
469
+ ]);
470
+ // Act
471
+ const state = detector.detectState(terminal, 'idle');
472
+ // Assert
473
+ expect(state).toBe('waiting_input');
474
+ });
475
+ it('detects busy when "Esc to cancel" is present', () => {
476
+ // Arrange
477
+ terminal = createMockTerminal([
478
+ 'Executing request...',
479
+ 'Press Esc to cancel',
480
+ ]);
481
+ // Act
482
+ const state = detector.detectState(terminal, 'idle');
483
+ // Assert
484
+ expect(state).toBe('busy');
485
+ });
486
+ it('prioritizes waiting_input over busy when both patterns exist', () => {
487
+ // Arrange
488
+ terminal = createMockTerminal([
489
+ 'Press Esc to cancel',
490
+ '│ Do you want to continue?',
491
+ ]);
492
+ // Act
493
+ const state = detector.detectState(terminal, 'idle');
494
+ // Assert
495
+ expect(state).toBe('waiting_input');
496
+ });
497
+ it('returns idle when no patterns match', () => {
498
+ // Arrange
499
+ terminal = createMockTerminal([
500
+ 'GitHub Copilot CLI ready.',
501
+ 'Type a command to begin.',
502
+ ]);
503
+ // Act
504
+ const state = detector.detectState(terminal, 'idle');
505
+ // Assert
506
+ expect(state).toBe('idle');
507
+ });
508
+ });
@@ -3,7 +3,7 @@ import type pkg from '@xterm/headless';
3
3
  import { GitStatus } from '../utils/gitStatus.js';
4
4
  export type Terminal = InstanceType<typeof pkg.Terminal>;
5
5
  export type SessionState = 'idle' | 'busy' | 'waiting_input';
6
- export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor';
6
+ export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor' | 'github-copilot';
7
7
  export interface Worktree {
8
8
  path: string;
9
9
  branch?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",