ccmanager 4.2.2 → 4.3.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 -0
- package/dist/components/ConfigureCommand.js +4 -0
- package/dist/services/stateDetector/claude.js +8 -0
- package/dist/services/stateDetector/claude.test.js +31 -0
- package/dist/services/stateDetector/index.js +3 -0
- package/dist/services/stateDetector/mcode.d.ts +12 -0
- package/dist/services/stateDetector/mcode.js +34 -0
- package/dist/services/stateDetector/mcode.test.d.ts +1 -0
- package/dist/services/stateDetector/mcode.test.js +56 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/presetPrompt.js +2 -1
- package/dist/utils/presetPrompt.test.js +18 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ CCManager supports multiple AI coding assistants with tailored state detection f
|
|
|
130
130
|
| Cline CLI | `cline` | [github.com/cline/cline](https://github.com/cline/cline) |
|
|
131
131
|
| OpenCode | `opencode` | [opencode.ai/docs](https://opencode.ai/docs) |
|
|
132
132
|
| Kimi CLI | `kimi` | [kimi-cli.com](https://www.kimi-cli.com/en/) |
|
|
133
|
+
| MiniMax Code (MCode) | `mcode` | [npmjs.com/package/@minimax-ai/code](https://www.npmjs.com/package/@minimax-ai/code) |
|
|
133
134
|
|
|
134
135
|
Each assistant has its own state detection strategy to properly track:
|
|
135
136
|
- **Idle**: Ready for new input
|
|
@@ -22,6 +22,7 @@ const createStrategyItems = () => {
|
|
|
22
22
|
cline: { label: 'Cline', value: 'cline' },
|
|
23
23
|
opencode: { label: 'OpenCode', value: 'opencode' },
|
|
24
24
|
kimi: { label: 'Kimi', value: 'kimi' },
|
|
25
|
+
mcode: { label: 'MiniMax Code', value: 'mcode' },
|
|
25
26
|
};
|
|
26
27
|
return Object.values(strategies);
|
|
27
28
|
};
|
|
@@ -37,6 +38,7 @@ const DEFAULT_COMMANDS = {
|
|
|
37
38
|
cline: 'cline',
|
|
38
39
|
opencode: 'opencode',
|
|
39
40
|
kimi: 'kimi',
|
|
41
|
+
mcode: 'mcode',
|
|
40
42
|
};
|
|
41
43
|
const formatDetectionStrategy = (strategy) => {
|
|
42
44
|
const value = strategy || 'claude';
|
|
@@ -55,6 +57,8 @@ const formatDetectionStrategy = (strategy) => {
|
|
|
55
57
|
return 'OpenCode';
|
|
56
58
|
case 'kimi':
|
|
57
59
|
return 'Kimi';
|
|
60
|
+
case 'mcode':
|
|
61
|
+
return 'MiniMax Code';
|
|
58
62
|
default:
|
|
59
63
|
return 'Claude';
|
|
60
64
|
}
|
|
@@ -111,6 +111,14 @@ export class ClaudeStateDetector extends BaseStateDetector {
|
|
|
111
111
|
if (fullLowerContent.includes('esc to cancel')) {
|
|
112
112
|
return 'waiting_input';
|
|
113
113
|
}
|
|
114
|
+
// Check for permission menus without question phrasing, e.g.
|
|
115
|
+
// "Claude in Chrome wants to navigate on example.com" with:
|
|
116
|
+
// ❯ 1. Allow
|
|
117
|
+
// 2. Deny (esc)
|
|
118
|
+
// The numbered "Deny (esc)" option is the stable marker across variants.
|
|
119
|
+
if (/\d+\.\s*deny\s*\(esc\)/.test(fullLowerContent)) {
|
|
120
|
+
return 'waiting_input';
|
|
121
|
+
}
|
|
114
122
|
// Content above the prompt box only for busy detection
|
|
115
123
|
const abovePromptBox = this.getRecentContentAbovePromptBox(terminal, 30);
|
|
116
124
|
const aboveLowerContent = abovePromptBox.toLowerCase();
|
|
@@ -237,6 +237,37 @@ describe('ClaudeStateDetector', () => {
|
|
|
237
237
|
// Assert
|
|
238
238
|
expect(state).toBe('waiting_input');
|
|
239
239
|
});
|
|
240
|
+
it('should detect waiting_input for Claude in Chrome permission prompt (Allow/Deny)', () => {
|
|
241
|
+
// Arrange - browser permission prompt without "Do you want" phrasing
|
|
242
|
+
terminal = createMockTerminal([
|
|
243
|
+
'──────────────────────────────',
|
|
244
|
+
' Claude in Chrome wants to create a browser window and read your tabs',
|
|
245
|
+
'',
|
|
246
|
+
' ❯ 1. Allow',
|
|
247
|
+
' 2. Deny (esc)',
|
|
248
|
+
]);
|
|
249
|
+
// Act
|
|
250
|
+
const state = detector.detectState(terminal, 'idle');
|
|
251
|
+
// Assert
|
|
252
|
+
expect(state).toBe('waiting_input');
|
|
253
|
+
});
|
|
254
|
+
it('should detect waiting_input for Claude in Chrome navigation prompt (Allow all / Deny)', () => {
|
|
255
|
+
// Arrange - three-option variant with "Allow all actions" entry
|
|
256
|
+
terminal = createMockTerminal([
|
|
257
|
+
'──────────────────────────────',
|
|
258
|
+
' Claude in Chrome wants to navigate on example.com',
|
|
259
|
+
'',
|
|
260
|
+
' https://example.com/app',
|
|
261
|
+
'',
|
|
262
|
+
' ❯ 1. Allow',
|
|
263
|
+
' 2. Allow all actions on example.com for this session',
|
|
264
|
+
' 3. Deny (esc)',
|
|
265
|
+
]);
|
|
266
|
+
// Act
|
|
267
|
+
const state = detector.detectState(terminal, 'idle');
|
|
268
|
+
// Assert
|
|
269
|
+
expect(state).toBe('waiting_input');
|
|
270
|
+
});
|
|
240
271
|
it('should prioritize "esc to cancel" over "esc to interrupt" when both above prompt box', () => {
|
|
241
272
|
// Arrange
|
|
242
273
|
terminal = createMockTerminal([
|
|
@@ -6,6 +6,7 @@ import { GitHubCopilotStateDetector } from './github-copilot.js';
|
|
|
6
6
|
import { ClineStateDetector } from './cline.js';
|
|
7
7
|
import { OpenCodeStateDetector } from './opencode.js';
|
|
8
8
|
import { KimiStateDetector } from './kimi.js';
|
|
9
|
+
import { MCodeStateDetector } from './mcode.js';
|
|
9
10
|
export function createStateDetector(strategy = 'claude') {
|
|
10
11
|
switch (strategy) {
|
|
11
12
|
case 'claude':
|
|
@@ -24,6 +25,8 @@ export function createStateDetector(strategy = 'claude') {
|
|
|
24
25
|
return new OpenCodeStateDetector();
|
|
25
26
|
case 'kimi':
|
|
26
27
|
return new KimiStateDetector();
|
|
28
|
+
case 'mcode':
|
|
29
|
+
return new MCodeStateDetector();
|
|
27
30
|
default:
|
|
28
31
|
return new ClaudeStateDetector();
|
|
29
32
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SessionState, Terminal } from '../../types/index.js';
|
|
2
|
+
import { BaseStateDetector } from './base.js';
|
|
3
|
+
/**
|
|
4
|
+
* State detector for MiniMax Code (MCode).
|
|
5
|
+
* Command: mcode
|
|
6
|
+
* Installation: npm install -g @minimax-ai/code
|
|
7
|
+
*/
|
|
8
|
+
export declare class MCodeStateDetector extends BaseStateDetector {
|
|
9
|
+
detectState(terminal: Terminal, _currentState: SessionState): SessionState;
|
|
10
|
+
detectBackgroundTask(_terminal: Terminal): number;
|
|
11
|
+
detectTeamMembers(_terminal: Terminal): number;
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseStateDetector } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* State detector for MiniMax Code (MCode).
|
|
4
|
+
* Command: mcode
|
|
5
|
+
* Installation: npm install -g @minimax-ai/code
|
|
6
|
+
*/
|
|
7
|
+
export class MCodeStateDetector extends BaseStateDetector {
|
|
8
|
+
detectState(terminal, _currentState) {
|
|
9
|
+
const content = this.getTerminalContent(terminal, 30);
|
|
10
|
+
const lowerContent = content.toLowerCase();
|
|
11
|
+
// MCode exposes pending interactive decisions in its runtime status and
|
|
12
|
+
// renders the event marker in the transcript. These take precedence over
|
|
13
|
+
// an active turn because the user must act before that turn can continue.
|
|
14
|
+
if (lowerContent.includes('permission: pending') ||
|
|
15
|
+
lowerContent.includes('[permission] permission.ask') ||
|
|
16
|
+
lowerContent.includes('ask_user: pending') ||
|
|
17
|
+
lowerContent.includes('[ask_user] questionnaire.ask')) {
|
|
18
|
+
return 'waiting_input';
|
|
19
|
+
}
|
|
20
|
+
// Current releases include a turn id; the compact fallback is emitted
|
|
21
|
+
// while a turn is starting. Match the status field rather than ordinary
|
|
22
|
+
// assistant text so historical words such as "thinking" cannot look busy.
|
|
23
|
+
if (/\bturn:\s+(?:\S+\s+)?running\b/i.test(content)) {
|
|
24
|
+
return 'busy';
|
|
25
|
+
}
|
|
26
|
+
return 'idle';
|
|
27
|
+
}
|
|
28
|
+
detectBackgroundTask(_terminal) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
detectTeamMembers(_terminal) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { createStateDetector } from './index.js';
|
|
3
|
+
import { MCodeStateDetector } from './mcode.js';
|
|
4
|
+
import { createMockTerminal } from './testUtils.js';
|
|
5
|
+
describe('MCodeStateDetector', () => {
|
|
6
|
+
let detector;
|
|
7
|
+
let terminal;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
detector = new MCodeStateDetector();
|
|
10
|
+
});
|
|
11
|
+
it('detects the published MCode ready footer as idle', () => {
|
|
12
|
+
terminal = createMockTerminal([
|
|
13
|
+
'╭─ v0.1.2 ● Ready ─╮',
|
|
14
|
+
'│ Start · @ file · / autocomplete │',
|
|
15
|
+
'╰─────────────────────────────────────────╯',
|
|
16
|
+
]);
|
|
17
|
+
expect(detector.detectState(terminal, 'busy')).toBe('idle');
|
|
18
|
+
});
|
|
19
|
+
it('is selected by the mcode strategy', () => {
|
|
20
|
+
expect(createStateDetector('mcode')).toBeInstanceOf(MCodeStateDetector);
|
|
21
|
+
});
|
|
22
|
+
it('detects an active runtime turn as busy', () => {
|
|
23
|
+
terminal = createMockTerminal([
|
|
24
|
+
'session: mvs_abc | turn: turn_123 running 3s | local-runtime',
|
|
25
|
+
]);
|
|
26
|
+
expect(detector.detectState(terminal, 'idle')).toBe('busy');
|
|
27
|
+
});
|
|
28
|
+
it('detects the compact running status as busy', () => {
|
|
29
|
+
terminal = createMockTerminal(['session: mvs_abc | turn: running']);
|
|
30
|
+
expect(detector.detectState(terminal, 'idle')).toBe('busy');
|
|
31
|
+
});
|
|
32
|
+
it.each([
|
|
33
|
+
'permission: pending',
|
|
34
|
+
'[permission] permission.ask',
|
|
35
|
+
'ask_user: pending',
|
|
36
|
+
'[ask_user] questionnaire.ask',
|
|
37
|
+
])('detects %s as waiting for input', marker => {
|
|
38
|
+
terminal = createMockTerminal([
|
|
39
|
+
'session: mvs_abc | turn: turn_123 running 3s',
|
|
40
|
+
marker,
|
|
41
|
+
]);
|
|
42
|
+
expect(detector.detectState(terminal, 'busy')).toBe('waiting_input');
|
|
43
|
+
});
|
|
44
|
+
it('does not mistake historical thinking text for an active turn', () => {
|
|
45
|
+
terminal = createMockTerminal([
|
|
46
|
+
'Assistant: Thinking through the previous request...',
|
|
47
|
+
'╭─ v0.1.2 ● Ready ─╮',
|
|
48
|
+
]);
|
|
49
|
+
expect(detector.detectState(terminal, 'busy')).toBe('idle');
|
|
50
|
+
});
|
|
51
|
+
it('reports no background tasks or team members', () => {
|
|
52
|
+
terminal = createMockTerminal([]);
|
|
53
|
+
expect(detector.detectBackgroundTask(terminal)).toBe(0);
|
|
54
|
+
expect(detector.detectTeamMembers(terminal)).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { Effect } from 'effect';
|
|
|
8
8
|
import type { GitError, FileSystemError, ProcessError } from './errors.js';
|
|
9
9
|
export type Terminal = InstanceType<typeof pkg.Terminal>;
|
|
10
10
|
export type SessionState = 'idle' | 'busy' | 'waiting_input' | 'pending_auto_approval';
|
|
11
|
-
export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor' | 'github-copilot' | 'cline' | 'opencode' | 'kimi';
|
|
11
|
+
export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex' | 'cursor' | 'github-copilot' | 'cline' | 'opencode' | 'kimi' | 'mcode';
|
|
12
12
|
export interface Worktree {
|
|
13
13
|
path: string;
|
|
14
14
|
branch?: string;
|
|
@@ -22,7 +22,8 @@ export const getPromptInjectionMethod = (preset) => {
|
|
|
22
22
|
if (strategy === 'claude' ||
|
|
23
23
|
strategy === 'codex' ||
|
|
24
24
|
strategy === 'cursor' ||
|
|
25
|
-
strategy === 'cline'
|
|
25
|
+
strategy === 'cline' ||
|
|
26
|
+
strategy === 'mcode') {
|
|
26
27
|
return 'final-arg';
|
|
27
28
|
}
|
|
28
29
|
return 'stdin';
|
|
@@ -49,6 +49,12 @@ describe('presetPrompt', () => {
|
|
|
49
49
|
method: 'flag',
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
+
it('uses the final argument for MCode presets', () => {
|
|
53
|
+
expect(preparePresetLaunch({ command: 'mcode', args: [], detectionStrategy: 'mcode' }, 'fix the failing tests')).toEqual({
|
|
54
|
+
args: ['fix the failing tests'],
|
|
55
|
+
method: 'final-arg',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
52
58
|
describe('describePromptInjection', () => {
|
|
53
59
|
it('describes final-arg for claude', () => {
|
|
54
60
|
expect(describePromptInjection({
|
|
@@ -98,6 +104,12 @@ describe('presetPrompt', () => {
|
|
|
98
104
|
detectionStrategy: 'kimi',
|
|
99
105
|
})).toContain('-p');
|
|
100
106
|
});
|
|
107
|
+
it('describes final-arg for MCode', () => {
|
|
108
|
+
expect(describePromptInjection({
|
|
109
|
+
command: 'mcode',
|
|
110
|
+
detectionStrategy: 'mcode',
|
|
111
|
+
})).toContain('final command argument');
|
|
112
|
+
});
|
|
101
113
|
});
|
|
102
114
|
describe('getPromptInjectionMethod', () => {
|
|
103
115
|
it('returns final-arg for claude', () => {
|
|
@@ -148,6 +160,12 @@ describe('presetPrompt', () => {
|
|
|
148
160
|
detectionStrategy: 'kimi',
|
|
149
161
|
})).toBe('flag');
|
|
150
162
|
});
|
|
163
|
+
it('returns final-arg for MCode', () => {
|
|
164
|
+
expect(getPromptInjectionMethod({
|
|
165
|
+
command: 'mcode',
|
|
166
|
+
detectionStrategy: 'mcode',
|
|
167
|
+
})).toBe('final-arg');
|
|
168
|
+
});
|
|
151
169
|
it('falls back to claude strategy when detectionStrategy is not set', () => {
|
|
152
170
|
expect(getPromptInjectionMethod({ command: 'claude' })).toBe('final-arg');
|
|
153
171
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kodai Kabasawa",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"bin"
|
|
42
42
|
],
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@kodaikabasawa/ccmanager-darwin-arm64": "4.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "4.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "4.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "4.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "4.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "4.3.0",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "4.3.0",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "4.3.0",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "4.3.0",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "4.3.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|