ccmanager 2.5.1 → 2.6.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.
|
@@ -5,6 +5,20 @@ import SelectInput from 'ink-select-input';
|
|
|
5
5
|
import { configurationManager } from '../services/configurationManager.js';
|
|
6
6
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
7
7
|
import Confirmation from './Confirmation.js';
|
|
8
|
+
// This function ensures all strategies are included at compile time
|
|
9
|
+
const createStrategyItems = () => {
|
|
10
|
+
// This object MUST include all StateDetectionStrategy values as keys
|
|
11
|
+
// If any are missing, TypeScript will error
|
|
12
|
+
const strategies = {
|
|
13
|
+
claude: { label: 'Claude', value: 'claude' },
|
|
14
|
+
gemini: { label: 'Gemini', value: 'gemini' },
|
|
15
|
+
codex: { label: 'Codex', value: 'codex' },
|
|
16
|
+
cursor: { label: 'Cursor Agent', value: 'cursor' },
|
|
17
|
+
};
|
|
18
|
+
return Object.values(strategies);
|
|
19
|
+
};
|
|
20
|
+
// Type-safe strategy items that ensures all StateDetectionStrategy values are included
|
|
21
|
+
const ALL_STRATEGY_ITEMS = createStrategyItems();
|
|
8
22
|
const formatDetectionStrategy = (strategy) => {
|
|
9
23
|
const value = strategy || 'claude';
|
|
10
24
|
switch (value) {
|
|
@@ -12,6 +26,8 @@ const formatDetectionStrategy = (strategy) => {
|
|
|
12
26
|
return 'Gemini';
|
|
13
27
|
case 'codex':
|
|
14
28
|
return 'Codex';
|
|
29
|
+
case 'cursor':
|
|
30
|
+
return 'Cursor';
|
|
15
31
|
default:
|
|
16
32
|
return 'Claude';
|
|
17
33
|
}
|
|
@@ -259,11 +275,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
259
275
|
const preset = presets.find(p => p.id === selectedPresetId);
|
|
260
276
|
if (!preset)
|
|
261
277
|
return null;
|
|
262
|
-
const strategyItems =
|
|
263
|
-
{ label: 'Claude', value: 'claude' },
|
|
264
|
-
{ label: 'Gemini', value: 'gemini' },
|
|
265
|
-
{ label: 'Codex', value: 'codex' },
|
|
266
|
-
];
|
|
278
|
+
const strategyItems = ALL_STRATEGY_ITEMS;
|
|
267
279
|
const currentStrategy = preset.detectionStrategy || 'claude';
|
|
268
280
|
const initialIndex = strategyItems.findIndex(item => item.value === currentStrategy);
|
|
269
281
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
@@ -308,11 +320,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
308
320
|
// Render add preset form
|
|
309
321
|
if (viewMode === 'add') {
|
|
310
322
|
if (isSelectingStrategyInAdd) {
|
|
311
|
-
const strategyItems =
|
|
312
|
-
{ label: 'Claude', value: 'claude' },
|
|
313
|
-
{ label: 'Gemini', value: 'gemini' },
|
|
314
|
-
{ label: 'Codex', value: 'codex' },
|
|
315
|
-
];
|
|
323
|
+
const strategyItems = ALL_STRATEGY_ITEMS;
|
|
316
324
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
317
325
|
React.createElement(Box, { marginBottom: 1 },
|
|
318
326
|
React.createElement(Text, { bold: true, color: "green" }, "Add New Preset - Detection Strategy")),
|
|
@@ -17,3 +17,6 @@ export declare class GeminiStateDetector extends BaseStateDetector {
|
|
|
17
17
|
export declare class CodexStateDetector extends BaseStateDetector {
|
|
18
18
|
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
19
19
|
}
|
|
20
|
+
export declare class CursorStateDetector extends BaseStateDetector {
|
|
21
|
+
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
22
|
+
}
|
|
@@ -6,6 +6,8 @@ export function createStateDetector(strategy = 'claude') {
|
|
|
6
6
|
return new GeminiStateDetector();
|
|
7
7
|
case 'codex':
|
|
8
8
|
return new CodexStateDetector();
|
|
9
|
+
case 'cursor':
|
|
10
|
+
return new CursorStateDetector();
|
|
9
11
|
default:
|
|
10
12
|
return new ClaudeStateDetector();
|
|
11
13
|
}
|
|
@@ -89,3 +91,21 @@ export class CodexStateDetector extends BaseStateDetector {
|
|
|
89
91
|
return 'idle';
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
export class CursorStateDetector extends BaseStateDetector {
|
|
95
|
+
detectState(terminal, _currentState) {
|
|
96
|
+
const content = this.getTerminalContent(terminal);
|
|
97
|
+
const lowerContent = content.toLowerCase();
|
|
98
|
+
// Check for waiting prompts - Priority 1
|
|
99
|
+
if (lowerContent.includes('(y) (enter)') ||
|
|
100
|
+
lowerContent.includes('keep (n)') ||
|
|
101
|
+
/auto .* \(shift\+tab\)/.test(lowerContent)) {
|
|
102
|
+
return 'waiting_input';
|
|
103
|
+
}
|
|
104
|
+
// Check for busy state - Priority 2
|
|
105
|
+
if (lowerContent.includes('ctrl+c to stop')) {
|
|
106
|
+
return 'busy';
|
|
107
|
+
}
|
|
108
|
+
// Otherwise idle - Priority 3
|
|
109
|
+
return 'idle';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, } from './stateDetector.js';
|
|
2
|
+
import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, CursorStateDetector, } from './stateDetector.js';
|
|
3
3
|
describe('ClaudeStateDetector', () => {
|
|
4
4
|
let detector;
|
|
5
5
|
let terminal;
|
|
@@ -363,3 +363,147 @@ describe('CodexStateDetector', () => {
|
|
|
363
363
|
expect(state).toBe('waiting_input');
|
|
364
364
|
});
|
|
365
365
|
});
|
|
366
|
+
describe('CursorStateDetector', () => {
|
|
367
|
+
let detector;
|
|
368
|
+
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
|
+
beforeEach(() => {
|
|
387
|
+
detector = new CursorStateDetector();
|
|
388
|
+
});
|
|
389
|
+
it('should detect waiting_input state for (y) (enter) pattern', () => {
|
|
390
|
+
// Arrange
|
|
391
|
+
terminal = createMockTerminal([
|
|
392
|
+
'Some output',
|
|
393
|
+
'Apply changes? (y) (enter)',
|
|
394
|
+
'> ',
|
|
395
|
+
]);
|
|
396
|
+
// Act
|
|
397
|
+
const state = detector.detectState(terminal, 'idle');
|
|
398
|
+
// Assert
|
|
399
|
+
expect(state).toBe('waiting_input');
|
|
400
|
+
});
|
|
401
|
+
it('should detect waiting_input state for (Y) (ENTER) pattern (case insensitive)', () => {
|
|
402
|
+
// Arrange
|
|
403
|
+
terminal = createMockTerminal([
|
|
404
|
+
'Some output',
|
|
405
|
+
'Continue? (Y) (ENTER)',
|
|
406
|
+
'> ',
|
|
407
|
+
]);
|
|
408
|
+
// Act
|
|
409
|
+
const state = detector.detectState(terminal, 'idle');
|
|
410
|
+
// Assert
|
|
411
|
+
expect(state).toBe('waiting_input');
|
|
412
|
+
});
|
|
413
|
+
it('should detect waiting_input state for Keep (n) pattern', () => {
|
|
414
|
+
// Arrange
|
|
415
|
+
terminal = createMockTerminal([
|
|
416
|
+
'Changes detected',
|
|
417
|
+
'Keep (n) or replace?',
|
|
418
|
+
'> ',
|
|
419
|
+
]);
|
|
420
|
+
// Act
|
|
421
|
+
const state = detector.detectState(terminal, 'idle');
|
|
422
|
+
// Assert
|
|
423
|
+
expect(state).toBe('waiting_input');
|
|
424
|
+
});
|
|
425
|
+
it('should detect waiting_input state for KEEP (N) pattern (case insensitive)', () => {
|
|
426
|
+
// Arrange
|
|
427
|
+
terminal = createMockTerminal([
|
|
428
|
+
'Some output',
|
|
429
|
+
'KEEP (N) current version?',
|
|
430
|
+
'> ',
|
|
431
|
+
]);
|
|
432
|
+
// Act
|
|
433
|
+
const state = detector.detectState(terminal, 'idle');
|
|
434
|
+
// Assert
|
|
435
|
+
expect(state).toBe('waiting_input');
|
|
436
|
+
});
|
|
437
|
+
it('should detect waiting_input state for Auto pattern with shift+tab', () => {
|
|
438
|
+
// Arrange
|
|
439
|
+
terminal = createMockTerminal([
|
|
440
|
+
'Some output',
|
|
441
|
+
'Auto apply changes (shift+tab)',
|
|
442
|
+
'> ',
|
|
443
|
+
]);
|
|
444
|
+
// Act
|
|
445
|
+
const state = detector.detectState(terminal, 'idle');
|
|
446
|
+
// Assert
|
|
447
|
+
expect(state).toBe('waiting_input');
|
|
448
|
+
});
|
|
449
|
+
it('should detect waiting_input state for AUTO with SHIFT+TAB (case insensitive)', () => {
|
|
450
|
+
// Arrange
|
|
451
|
+
terminal = createMockTerminal([
|
|
452
|
+
'Some output',
|
|
453
|
+
'AUTO COMPLETE (SHIFT+TAB)',
|
|
454
|
+
'> ',
|
|
455
|
+
]);
|
|
456
|
+
// Act
|
|
457
|
+
const state = detector.detectState(terminal, 'idle');
|
|
458
|
+
// Assert
|
|
459
|
+
expect(state).toBe('waiting_input');
|
|
460
|
+
});
|
|
461
|
+
it('should detect busy state for ctrl+c to stop pattern', () => {
|
|
462
|
+
// Arrange
|
|
463
|
+
terminal = createMockTerminal([
|
|
464
|
+
'Processing...',
|
|
465
|
+
'Press ctrl+c to stop',
|
|
466
|
+
'Working...',
|
|
467
|
+
]);
|
|
468
|
+
// Act
|
|
469
|
+
const state = detector.detectState(terminal, 'idle');
|
|
470
|
+
// Assert
|
|
471
|
+
expect(state).toBe('busy');
|
|
472
|
+
});
|
|
473
|
+
it('should detect busy state for CTRL+C TO STOP (case insensitive)', () => {
|
|
474
|
+
// Arrange
|
|
475
|
+
terminal = createMockTerminal([
|
|
476
|
+
'Running...',
|
|
477
|
+
'PRESS CTRL+C TO STOP',
|
|
478
|
+
'Processing...',
|
|
479
|
+
]);
|
|
480
|
+
// Act
|
|
481
|
+
const state = detector.detectState(terminal, 'idle');
|
|
482
|
+
// Assert
|
|
483
|
+
expect(state).toBe('busy');
|
|
484
|
+
});
|
|
485
|
+
it('should detect idle state when no patterns match', () => {
|
|
486
|
+
// Arrange
|
|
487
|
+
terminal = createMockTerminal(['Normal output', 'Some message', 'Ready']);
|
|
488
|
+
// Act
|
|
489
|
+
const state = detector.detectState(terminal, 'idle');
|
|
490
|
+
// Assert
|
|
491
|
+
expect(state).toBe('idle');
|
|
492
|
+
});
|
|
493
|
+
it('should prioritize waiting_input over busy (Priority 1)', () => {
|
|
494
|
+
// Arrange
|
|
495
|
+
terminal = createMockTerminal(['ctrl+c to stop', '(y) (enter)']);
|
|
496
|
+
// Act
|
|
497
|
+
const state = detector.detectState(terminal, 'idle');
|
|
498
|
+
// Assert
|
|
499
|
+
expect(state).toBe('waiting_input'); // waiting_input should take precedence
|
|
500
|
+
});
|
|
501
|
+
it('should handle empty terminal', () => {
|
|
502
|
+
// Arrange
|
|
503
|
+
terminal = createMockTerminal([]);
|
|
504
|
+
// Act
|
|
505
|
+
const state = detector.detectState(terminal, 'idle');
|
|
506
|
+
// Assert
|
|
507
|
+
expect(state).toBe('idle');
|
|
508
|
+
});
|
|
509
|
+
});
|
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';
|
|
6
|
+
export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor';
|
|
7
7
|
export interface Worktree {
|
|
8
8
|
path: string;
|
|
9
9
|
branch?: string;
|