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 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: 'command',
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 === 'command') {
55
- setView('command');
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('command');
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 === 'command') {
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
+ });
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "2.5.1",
3
+ "version": "2.6.1",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",