ccmanager 2.5.1 → 2.6.1
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 +19 -11
- package/dist/services/stateDetector.d.ts +3 -0
- package/dist/services/stateDetector.js +20 -0
- package/dist/services/stateDetector.test.js +145 -1
- 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" },
|
|
@@ -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")),
|
|
@@ -503,7 +511,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
503
511
|
};
|
|
504
512
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
505
513
|
React.createElement(Box, { marginBottom: 1 },
|
|
506
|
-
React.createElement(Text, { bold: true, color: "green" }, "Command Presets")),
|
|
514
|
+
React.createElement(Text, { bold: true, color: "green" }, "Command Command Presets")),
|
|
507
515
|
React.createElement(Box, { marginBottom: 1 },
|
|
508
516
|
React.createElement(Text, { dimColor: true }, "Configure command presets for running code sessions")),
|
|
509
517
|
React.createElement(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: selectedIndex }),
|
|
@@ -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;
|