ccmanager 2.11.3 → 2.11.4

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.
@@ -1,705 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, CursorStateDetector, GitHubCopilotStateDetector, ClineStateDetector, } from './stateDetector.js';
3
- const createMockTerminal = (lines) => {
4
- const buffer = {
5
- length: lines.length,
6
- active: {
7
- length: lines.length,
8
- getLine: (index) => {
9
- if (index >= 0 && index < lines.length) {
10
- return {
11
- translateToString: () => lines[index],
12
- };
13
- }
14
- return null;
15
- },
16
- },
17
- };
18
- return { buffer };
19
- };
20
- describe('ClaudeStateDetector', () => {
21
- let detector;
22
- let terminal;
23
- beforeEach(() => {
24
- detector = new ClaudeStateDetector();
25
- });
26
- describe('detectState', () => {
27
- it('should detect waiting_input when "Do you want" prompt is present', () => {
28
- // Arrange
29
- terminal = createMockTerminal([
30
- 'Some previous output',
31
- '│ Do you want to continue? (y/n)',
32
- '│ > ',
33
- ]);
34
- // Act
35
- const state = detector.detectState(terminal, 'idle');
36
- // Assert
37
- expect(state).toBe('waiting_input');
38
- });
39
- it('should detect waiting_input when "Would you like" prompt is present', () => {
40
- // Arrange
41
- terminal = createMockTerminal([
42
- 'Some output',
43
- '│ Would you like to save changes?',
44
- '│ > ',
45
- ]);
46
- // Act
47
- const state = detector.detectState(terminal, 'idle');
48
- // Assert
49
- expect(state).toBe('waiting_input');
50
- });
51
- it('should detect busy when "ESC to interrupt" is present', () => {
52
- // Arrange
53
- terminal = createMockTerminal([
54
- 'Processing...',
55
- 'Press ESC to interrupt',
56
- ]);
57
- // Act
58
- const state = detector.detectState(terminal, 'idle');
59
- // Assert
60
- expect(state).toBe('busy');
61
- });
62
- it('should detect busy when "esc to interrupt" is present (case insensitive)', () => {
63
- // Arrange
64
- terminal = createMockTerminal([
65
- 'Running command...',
66
- 'press esc to interrupt the process',
67
- ]);
68
- // Act
69
- const state = detector.detectState(terminal, 'idle');
70
- // Assert
71
- expect(state).toBe('busy');
72
- });
73
- it('should detect idle when no specific patterns are found', () => {
74
- // Arrange
75
- terminal = createMockTerminal([
76
- 'Command completed successfully',
77
- 'Ready for next command',
78
- '> ',
79
- ]);
80
- // Act
81
- const state = detector.detectState(terminal, 'idle');
82
- // Assert
83
- expect(state).toBe('idle');
84
- });
85
- it('should handle empty terminal', () => {
86
- // Arrange
87
- terminal = createMockTerminal([]);
88
- // Act
89
- const state = detector.detectState(terminal, 'idle');
90
- // Assert
91
- expect(state).toBe('idle');
92
- });
93
- it('should only consider last 30 lines', () => {
94
- // Arrange
95
- const lines = [];
96
- // Add more than 30 lines
97
- for (let i = 0; i < 40; i++) {
98
- lines.push(`Line ${i}`);
99
- }
100
- // The "Do you want" should be outside the 30 line window
101
- lines.push('│ Do you want to continue?');
102
- // Add 30 more lines to push it out
103
- for (let i = 0; i < 30; i++) {
104
- lines.push(`Recent line ${i}`);
105
- }
106
- terminal = createMockTerminal(lines);
107
- // Act
108
- const state = detector.detectState(terminal, 'idle');
109
- // Assert
110
- expect(state).toBe('idle'); // Should not detect the old prompt
111
- });
112
- it('should prioritize waiting_input over busy state', () => {
113
- // Arrange
114
- terminal = createMockTerminal([
115
- 'Press ESC to interrupt',
116
- '│ Do you want to continue?',
117
- '│ > ',
118
- ]);
119
- // Act
120
- const state = detector.detectState(terminal, 'idle');
121
- // Assert
122
- expect(state).toBe('waiting_input'); // waiting_input should take precedence
123
- });
124
- it('should maintain current state when "ctrl+r to toggle" is present', () => {
125
- // Arrange
126
- terminal = createMockTerminal([
127
- 'Some output',
128
- 'Press Ctrl+R to toggle history search',
129
- 'More output',
130
- ]);
131
- // Act - test with different current states
132
- const idleState = detector.detectState(terminal, 'idle');
133
- const busyState = detector.detectState(terminal, 'busy');
134
- const waitingState = detector.detectState(terminal, 'waiting_input');
135
- // Assert - should maintain whatever the current state was
136
- expect(idleState).toBe('idle');
137
- expect(busyState).toBe('busy');
138
- expect(waitingState).toBe('waiting_input');
139
- });
140
- it('should maintain current state for various "ctrl+r" patterns', () => {
141
- // Arrange - test different case variations
142
- const patterns = [
143
- 'ctrl+r to toggle',
144
- 'CTRL+R TO TOGGLE',
145
- 'Ctrl+R to toggle history',
146
- 'Press ctrl+r to toggle the search',
147
- ];
148
- for (const pattern of patterns) {
149
- terminal = createMockTerminal(['Some output', pattern]);
150
- // Act
151
- const state = detector.detectState(terminal, 'busy');
152
- // Assert - should maintain the current state
153
- expect(state).toBe('busy');
154
- }
155
- });
156
- it('should detect waiting_input when "Do you want" with options prompt is present', () => {
157
- // Arrange
158
- terminal = createMockTerminal([
159
- 'Some previous output',
160
- 'Do you want to make this edit to test.txt?',
161
- '❯ 1. Yes',
162
- '2. Yes, allow all edits during this session (shift+tab)',
163
- '3. No, and tell Claude what to do differently (esc)',
164
- ]);
165
- // Act
166
- const state = detector.detectState(terminal, 'idle');
167
- // Assert
168
- expect(state).toBe('waiting_input');
169
- });
170
- it('should detect waiting_input when "Do you want" with options prompt is present (case insensitive)', () => {
171
- // Arrange
172
- terminal = createMockTerminal([
173
- 'Some output',
174
- 'DO YOU WANT to make this edit?',
175
- '❯ 1. YES',
176
- '2. NO',
177
- ]);
178
- // Act
179
- const state = detector.detectState(terminal, 'idle');
180
- // Assert
181
- expect(state).toBe('waiting_input');
182
- });
183
- it('should prioritize "Do you want" with options over busy state', () => {
184
- // Arrange
185
- terminal = createMockTerminal([
186
- 'Press ESC to interrupt',
187
- 'Do you want to continue?',
188
- '❯ 1. Yes',
189
- '2. No',
190
- ]);
191
- // Act
192
- const state = detector.detectState(terminal, 'idle');
193
- // Assert
194
- expect(state).toBe('waiting_input'); // waiting_input should take precedence
195
- });
196
- it('should detect waiting_input with "Would you like" and multiple numbered options', () => {
197
- // Arrange
198
- terminal = createMockTerminal([
199
- 'Some previous output',
200
- 'Would you like to proceed?',
201
- '',
202
- '❯ 1. Yes, and auto-accept edits',
203
- ' 2. Yes, and manually approve edits',
204
- ' 3. No, keep planning',
205
- ]);
206
- // Act
207
- const state = detector.detectState(terminal, 'idle');
208
- // Assert
209
- expect(state).toBe('waiting_input');
210
- });
211
- it('should detect waiting_input with complex multi-line prompt and cursor indicator', () => {
212
- // Arrange
213
- terminal = createMockTerminal([
214
- 'Processing complete.',
215
- 'Would you like to apply these changes?',
216
- '',
217
- '❯ 1. Yes, apply all changes',
218
- ' 2. Yes, review changes first',
219
- ' 3. No, discard changes',
220
- ' 4. Cancel operation',
221
- ]);
222
- // Act
223
- const state = detector.detectState(terminal, 'idle');
224
- // Assert
225
- expect(state).toBe('waiting_input');
226
- });
227
- it('should detect waiting_input when cursor indicator is present without explicit "yes" text', () => {
228
- // Arrange
229
- terminal = createMockTerminal([
230
- 'Do you want to proceed?',
231
- '',
232
- '❯ 1. Apply all',
233
- ' 2. Review first',
234
- ' 3. Skip',
235
- ]);
236
- // Act
237
- const state = detector.detectState(terminal, 'idle');
238
- // Assert
239
- expect(state).toBe('waiting_input');
240
- });
241
- });
242
- });
243
- describe('GeminiStateDetector', () => {
244
- let detector;
245
- let terminal;
246
- beforeEach(() => {
247
- detector = new GeminiStateDetector();
248
- });
249
- describe('detectState', () => {
250
- it('should detect waiting_input when "Apply this change?" prompt is present', () => {
251
- // Arrange
252
- terminal = createMockTerminal([
253
- 'Some output from Gemini',
254
- '│ Apply this change?',
255
- '│ > ',
256
- ]);
257
- // Act
258
- const state = detector.detectState(terminal, 'idle');
259
- // Assert
260
- expect(state).toBe('waiting_input');
261
- });
262
- it('should detect waiting_input when "Allow execution?" prompt is present', () => {
263
- // Arrange
264
- terminal = createMockTerminal([
265
- 'Command found: npm install',
266
- '│ Allow execution?',
267
- '│ > ',
268
- ]);
269
- // Act
270
- const state = detector.detectState(terminal, 'idle');
271
- // Assert
272
- expect(state).toBe('waiting_input');
273
- });
274
- it('should detect waiting_input when "Do you want to proceed?" prompt is present', () => {
275
- // Arrange
276
- terminal = createMockTerminal([
277
- 'Changes detected',
278
- '│ Do you want to proceed?',
279
- '│ > ',
280
- ]);
281
- // Act
282
- const state = detector.detectState(terminal, 'idle');
283
- // Assert
284
- expect(state).toBe('waiting_input');
285
- });
286
- it('should detect busy when "esc to cancel" is present', () => {
287
- // Arrange
288
- terminal = createMockTerminal([
289
- 'Processing your request...',
290
- 'Press ESC to cancel',
291
- ]);
292
- // Act
293
- const state = detector.detectState(terminal, 'idle');
294
- // Assert
295
- expect(state).toBe('busy');
296
- });
297
- it('should detect busy when "ESC to cancel" is present (case insensitive)', () => {
298
- // Arrange
299
- terminal = createMockTerminal([
300
- 'Running command...',
301
- 'Press Esc to cancel the operation',
302
- ]);
303
- // Act
304
- const state = detector.detectState(terminal, 'idle');
305
- // Assert
306
- expect(state).toBe('busy');
307
- });
308
- it('should detect idle when no specific patterns are found', () => {
309
- // Arrange
310
- terminal = createMockTerminal([
311
- 'Welcome to Gemini CLI',
312
- 'Type your message below',
313
- ]);
314
- // Act
315
- const state = detector.detectState(terminal, 'idle');
316
- // Assert
317
- expect(state).toBe('idle');
318
- });
319
- it('should handle empty terminal', () => {
320
- // Arrange
321
- terminal = createMockTerminal([]);
322
- // Act
323
- const state = detector.detectState(terminal, 'idle');
324
- // Assert
325
- expect(state).toBe('idle');
326
- });
327
- it('should prioritize waiting_input over busy state', () => {
328
- // Arrange
329
- terminal = createMockTerminal([
330
- 'Press ESC to cancel',
331
- '│ Apply this change?',
332
- '│ > ',
333
- ]);
334
- // Act
335
- const state = detector.detectState(terminal, 'idle');
336
- // Assert
337
- expect(state).toBe('waiting_input'); // waiting_input should take precedence
338
- });
339
- });
340
- });
341
- describe('CodexStateDetector', () => {
342
- let detector;
343
- let terminal;
344
- beforeEach(() => {
345
- detector = new CodexStateDetector();
346
- });
347
- it('should detect waiting_input state for Allow command? pattern', () => {
348
- // Arrange
349
- terminal = createMockTerminal(['Some output', 'Allow command?', '│ > ']);
350
- // Act
351
- const state = detector.detectState(terminal, 'idle');
352
- // Assert
353
- expect(state).toBe('waiting_input');
354
- });
355
- it('should detect waiting_input state for [y/n] pattern', () => {
356
- // Arrange
357
- terminal = createMockTerminal(['Some output', 'Continue? [y/n]', '> ']);
358
- // Act
359
- const state = detector.detectState(terminal, 'idle');
360
- // Assert
361
- expect(state).toBe('waiting_input');
362
- });
363
- it('should detect waiting_input state for yes (y) pattern', () => {
364
- // Arrange
365
- terminal = createMockTerminal([
366
- 'Some output',
367
- 'Apply changes? yes (y) / no (n)',
368
- ]);
369
- // Act
370
- const state = detector.detectState(terminal, 'idle');
371
- // Assert
372
- expect(state).toBe('waiting_input');
373
- });
374
- it('should detect busy state for Esc to interrupt pattern', () => {
375
- // Arrange
376
- terminal = createMockTerminal([
377
- 'Processing...',
378
- 'Esc to interrupt',
379
- 'Working...',
380
- ]);
381
- // Act
382
- const state = detector.detectState(terminal, 'idle');
383
- // Assert
384
- expect(state).toBe('busy');
385
- });
386
- it('should detect busy state for ESC INTERRUPT (uppercase)', () => {
387
- // Arrange
388
- terminal = createMockTerminal([
389
- 'Processing...',
390
- 'PRESS ESC TO INTERRUPT',
391
- 'Working...',
392
- ]);
393
- // Act
394
- const state = detector.detectState(terminal, 'idle');
395
- // Assert
396
- expect(state).toBe('busy');
397
- });
398
- it('should detect idle state when no patterns match', () => {
399
- // Arrange
400
- terminal = createMockTerminal(['Normal output', 'Some message', 'Ready']);
401
- // Act
402
- const state = detector.detectState(terminal, 'idle');
403
- // Assert
404
- expect(state).toBe('idle');
405
- });
406
- it('should prioritize waiting_input over busy', () => {
407
- // Arrange
408
- terminal = createMockTerminal(['press esc to interrupt', '[y/n]']);
409
- // Act
410
- const state = detector.detectState(terminal, 'idle');
411
- // Assert
412
- expect(state).toBe('waiting_input');
413
- });
414
- });
415
- describe('CursorStateDetector', () => {
416
- let detector;
417
- let terminal;
418
- beforeEach(() => {
419
- detector = new CursorStateDetector();
420
- });
421
- it('should detect waiting_input state for (y) (enter) pattern', () => {
422
- // Arrange
423
- terminal = createMockTerminal([
424
- 'Some output',
425
- 'Apply changes? (y) (enter)',
426
- '> ',
427
- ]);
428
- // Act
429
- const state = detector.detectState(terminal, 'idle');
430
- // Assert
431
- expect(state).toBe('waiting_input');
432
- });
433
- it('should detect waiting_input state for (Y) (ENTER) pattern (case insensitive)', () => {
434
- // Arrange
435
- terminal = createMockTerminal([
436
- 'Some output',
437
- 'Continue? (Y) (ENTER)',
438
- '> ',
439
- ]);
440
- // Act
441
- const state = detector.detectState(terminal, 'idle');
442
- // Assert
443
- expect(state).toBe('waiting_input');
444
- });
445
- it('should detect waiting_input state for Keep (n) pattern', () => {
446
- // Arrange
447
- terminal = createMockTerminal([
448
- 'Changes detected',
449
- 'Keep (n) or replace?',
450
- '> ',
451
- ]);
452
- // Act
453
- const state = detector.detectState(terminal, 'idle');
454
- // Assert
455
- expect(state).toBe('waiting_input');
456
- });
457
- it('should detect waiting_input state for KEEP (N) pattern (case insensitive)', () => {
458
- // Arrange
459
- terminal = createMockTerminal([
460
- 'Some output',
461
- 'KEEP (N) current version?',
462
- '> ',
463
- ]);
464
- // Act
465
- const state = detector.detectState(terminal, 'idle');
466
- // Assert
467
- expect(state).toBe('waiting_input');
468
- });
469
- it('should detect waiting_input state for Auto pattern with shift+tab', () => {
470
- // Arrange
471
- terminal = createMockTerminal([
472
- 'Some output',
473
- 'Auto apply changes (shift+tab)',
474
- '> ',
475
- ]);
476
- // Act
477
- const state = detector.detectState(terminal, 'idle');
478
- // Assert
479
- expect(state).toBe('waiting_input');
480
- });
481
- it('should detect waiting_input state for AUTO with SHIFT+TAB (case insensitive)', () => {
482
- // Arrange
483
- terminal = createMockTerminal([
484
- 'Some output',
485
- 'AUTO COMPLETE (SHIFT+TAB)',
486
- '> ',
487
- ]);
488
- // Act
489
- const state = detector.detectState(terminal, 'idle');
490
- // Assert
491
- expect(state).toBe('waiting_input');
492
- });
493
- it('should detect busy state for ctrl+c to stop pattern', () => {
494
- // Arrange
495
- terminal = createMockTerminal([
496
- 'Processing...',
497
- 'Press ctrl+c to stop',
498
- 'Working...',
499
- ]);
500
- // Act
501
- const state = detector.detectState(terminal, 'idle');
502
- // Assert
503
- expect(state).toBe('busy');
504
- });
505
- it('should detect busy state for CTRL+C TO STOP (case insensitive)', () => {
506
- // Arrange
507
- terminal = createMockTerminal([
508
- 'Running...',
509
- 'PRESS CTRL+C TO STOP',
510
- 'Processing...',
511
- ]);
512
- // Act
513
- const state = detector.detectState(terminal, 'idle');
514
- // Assert
515
- expect(state).toBe('busy');
516
- });
517
- it('should detect idle state when no patterns match', () => {
518
- // Arrange
519
- terminal = createMockTerminal(['Normal output', 'Some message', 'Ready']);
520
- // Act
521
- const state = detector.detectState(terminal, 'idle');
522
- // Assert
523
- expect(state).toBe('idle');
524
- });
525
- it('should prioritize waiting_input over busy (Priority 1)', () => {
526
- // Arrange
527
- terminal = createMockTerminal(['ctrl+c to stop', '(y) (enter)']);
528
- // Act
529
- const state = detector.detectState(terminal, 'idle');
530
- // Assert
531
- expect(state).toBe('waiting_input'); // waiting_input should take precedence
532
- });
533
- it('should handle empty terminal', () => {
534
- // Arrange
535
- terminal = createMockTerminal([]);
536
- // Act
537
- const state = detector.detectState(terminal, 'idle');
538
- // Assert
539
- expect(state).toBe('idle');
540
- });
541
- });
542
- describe('GitHubCopilotStateDetector', () => {
543
- let detector;
544
- let terminal;
545
- beforeEach(() => {
546
- detector = new GitHubCopilotStateDetector();
547
- });
548
- it('detects waiting_input when prompt asks "Do you want" (case insensitive)', () => {
549
- // Arrange
550
- terminal = createMockTerminal([
551
- 'Running GitHub Copilot CLI...',
552
- '│ DO YOU WANT to run this command?',
553
- '│ > ',
554
- ]);
555
- // Act
556
- const state = detector.detectState(terminal, 'idle');
557
- // Assert
558
- expect(state).toBe('waiting_input');
559
- });
560
- it('detects busy when "Esc to cancel" is present', () => {
561
- // Arrange
562
- terminal = createMockTerminal([
563
- 'Executing request...',
564
- 'Press Esc to cancel',
565
- ]);
566
- // Act
567
- const state = detector.detectState(terminal, 'idle');
568
- // Assert
569
- expect(state).toBe('busy');
570
- });
571
- it('prioritizes waiting_input over busy when both patterns exist', () => {
572
- // Arrange
573
- terminal = createMockTerminal([
574
- 'Press Esc to cancel',
575
- '│ Do you want to continue?',
576
- ]);
577
- // Act
578
- const state = detector.detectState(terminal, 'idle');
579
- // Assert
580
- expect(state).toBe('waiting_input');
581
- });
582
- it('returns idle when no patterns match', () => {
583
- // Arrange
584
- terminal = createMockTerminal([
585
- 'GitHub Copilot CLI ready.',
586
- 'Type a command to begin.',
587
- ]);
588
- // Act
589
- const state = detector.detectState(terminal, 'idle');
590
- // Assert
591
- expect(state).toBe('idle');
592
- });
593
- });
594
- describe('ClineStateDetector', () => {
595
- let detector;
596
- let terminal;
597
- beforeEach(() => {
598
- detector = new ClineStateDetector();
599
- });
600
- it('should detect waiting_input when "Let Cline use this tool?" is present', () => {
601
- // Arrange
602
- terminal = createMockTerminal([
603
- '┃ [act mode] Let Cline use this tool?',
604
- '┃ > Yes',
605
- "┃ Yes, and don't ask again for this task",
606
- '┃ No, with feedback',
607
- ]);
608
- // Act
609
- const state = detector.detectState(terminal, 'idle');
610
- // Assert
611
- expect(state).toBe('waiting_input');
612
- });
613
- it('should detect waiting_input when "let cline use this tool?" is present (case insensitive)', () => {
614
- // Arrange
615
- terminal = createMockTerminal([
616
- 'Some output',
617
- 'LET CLINE USE THIS TOOL?',
618
- '> Yes',
619
- ]);
620
- // Act
621
- const state = detector.detectState(terminal, 'idle');
622
- // Assert
623
- expect(state).toBe('waiting_input');
624
- });
625
- it('should detect idle when "Cline is ready for your message" is present in act mode', () => {
626
- // Arrange
627
- terminal = createMockTerminal([
628
- '┃ [act mode] Cline is ready for your message...',
629
- '┃ /plan or /act to switch modes',
630
- '┃ ctrl+e to open editor',
631
- ]);
632
- // Act
633
- const state = detector.detectState(terminal, 'idle');
634
- // Assert
635
- expect(state).toBe('idle');
636
- });
637
- it('should detect idle when "Cline is ready for your message" is present in plan mode', () => {
638
- // Arrange
639
- terminal = createMockTerminal([
640
- '┃ [plan mode] Cline is ready for your message...',
641
- '┃ /plan or /act to switch modes',
642
- '┃ ctrl+e to open editor',
643
- ]);
644
- // Act
645
- const state = detector.detectState(terminal, 'idle');
646
- // Assert
647
- expect(state).toBe('idle');
648
- });
649
- it('should detect idle when "cline is ready" is present (case insensitive)', () => {
650
- // Arrange
651
- terminal = createMockTerminal([
652
- 'Some output',
653
- 'CLINE IS READY FOR YOUR MESSAGE',
654
- 'Ready to go',
655
- ]);
656
- // Act
657
- const state = detector.detectState(terminal, 'idle');
658
- // Assert
659
- expect(state).toBe('idle');
660
- });
661
- it('should detect busy when no specific patterns are found', () => {
662
- // Arrange
663
- terminal = createMockTerminal([
664
- 'Processing your request...',
665
- 'Running analysis...',
666
- 'Working on it...',
667
- ]);
668
- // Act
669
- const state = detector.detectState(terminal, 'idle');
670
- // Assert
671
- expect(state).toBe('busy');
672
- });
673
- it('should handle empty terminal as busy', () => {
674
- // Arrange
675
- terminal = createMockTerminal([]);
676
- // Act
677
- const state = detector.detectState(terminal, 'idle');
678
- // Assert
679
- expect(state).toBe('busy');
680
- });
681
- it('should prioritize waiting_input over idle', () => {
682
- // Arrange
683
- terminal = createMockTerminal([
684
- '┃ [act mode] Cline is ready for your message...',
685
- '┃ Let Cline use this tool?',
686
- '┃ > Yes',
687
- ]);
688
- // Act
689
- const state = detector.detectState(terminal, 'idle');
690
- // Assert
691
- expect(state).toBe('waiting_input'); // waiting_input should take precedence
692
- });
693
- it('should prioritize idle over busy', () => {
694
- // Arrange
695
- terminal = createMockTerminal([
696
- 'Processing...',
697
- 'Working...',
698
- '┃ [act mode] Cline is ready for your message...',
699
- ]);
700
- // Act
701
- const state = detector.detectState(terminal, 'idle');
702
- // Assert
703
- expect(state).toBe('idle'); // idle should take precedence over busy
704
- });
705
- });