ccmanager 2.6.0 → 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.
- package/README.md +1 -1
- package/dist/components/Configuration.js +6 -6
- package/dist/components/ConfigureCommand.js +7 -1
- package/dist/services/stateDetector.d.ts +3 -0
- package/dist/services/stateDetector.js +18 -0
- package/dist/services/stateDetector.test.js +63 -64
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ CCManager supports configuring the command and arguments used to run Claude Code
|
|
|
146
146
|
|
|
147
147
|
### Quick Start
|
|
148
148
|
|
|
149
|
-
1. Navigate to **Configuration** → **Configure Command**
|
|
149
|
+
1. Navigate to **Configuration** → **Configure Command Presets**
|
|
150
150
|
2. Set your desired arguments (e.g., `--resume` for resuming sessions)
|
|
151
151
|
3. Optionally set fallback arguments
|
|
152
152
|
4. Save changes
|
|
@@ -27,8 +27,8 @@ const Configuration = ({ onComplete }) => {
|
|
|
27
27
|
value: 'worktree',
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
label: 'C 🚀 Configure Command',
|
|
31
|
-
value: '
|
|
30
|
+
label: 'C 🚀 Configure Command Presets',
|
|
31
|
+
value: 'presets',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
label: 'B ← Back to Main Menu',
|
|
@@ -51,8 +51,8 @@ const Configuration = ({ onComplete }) => {
|
|
|
51
51
|
else if (item.value === 'worktree') {
|
|
52
52
|
setView('worktree');
|
|
53
53
|
}
|
|
54
|
-
else if (item.value === '
|
|
55
|
-
setView('
|
|
54
|
+
else if (item.value === 'presets') {
|
|
55
|
+
setView('presets');
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
const handleSubMenuComplete = () => {
|
|
@@ -77,7 +77,7 @@ const Configuration = ({ onComplete }) => {
|
|
|
77
77
|
setView('worktree');
|
|
78
78
|
break;
|
|
79
79
|
case 'c':
|
|
80
|
-
setView('
|
|
80
|
+
setView('presets');
|
|
81
81
|
break;
|
|
82
82
|
case 'b':
|
|
83
83
|
onComplete();
|
|
@@ -100,7 +100,7 @@ const Configuration = ({ onComplete }) => {
|
|
|
100
100
|
if (view === 'worktree') {
|
|
101
101
|
return React.createElement(ConfigureWorktree, { onComplete: handleSubMenuComplete });
|
|
102
102
|
}
|
|
103
|
-
if (view === '
|
|
103
|
+
if (view === 'presets') {
|
|
104
104
|
return React.createElement(ConfigureCommand, { onComplete: handleSubMenuComplete });
|
|
105
105
|
}
|
|
106
106
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
@@ -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
|
}
|
|
@@ -511,7 +517,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
511
517
|
};
|
|
512
518
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
513
519
|
React.createElement(Box, { marginBottom: 1 },
|
|
514
|
-
React.createElement(Text, { bold: true, color: "green" }, "Command Presets")),
|
|
520
|
+
React.createElement(Text, { bold: true, color: "green" }, "Command Command Presets")),
|
|
515
521
|
React.createElement(Box, { marginBottom: 1 },
|
|
516
522
|
React.createElement(Text, { dimColor: true }, "Configure command presets for running code sessions")),
|
|
517
523
|
React.createElement(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: selectedIndex }),
|
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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;
|