ccmanager 3.3.0 → 3.3.2
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 +12 -11
- package/dist/components/ConfigureCommand.js +66 -40
- package/dist/components/Session.js +3 -65
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -112,23 +112,24 @@ Note: Shortcuts from `shortcuts.json` will be automatically migrated to `config.
|
|
|
112
112
|
|
|
113
113
|
## Supported AI Assistants
|
|
114
114
|
|
|
115
|
-
CCManager
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
CCManager supports multiple AI coding assistants with tailored state detection for each:
|
|
116
|
+
|
|
117
|
+
| Assistant | Command | Installation |
|
|
118
|
+
|-----------|---------|--------------|
|
|
119
|
+
| Claude Code (Default) | `claude` | [code.claude.com](https://code.claude.com/docs/en/setup) |
|
|
120
|
+
| Gemini CLI | `gemini` | [github.com/google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli) |
|
|
121
|
+
| Codex CLI | `codex` | [github.com/openai/codex](https://github.com/openai/codex) |
|
|
122
|
+
| Cursor Agent | `cursor-agent` | [cursor.com/cli](https://cursor.com/docs/cli/overview) |
|
|
123
|
+
| Copilot CLI | `copilot` | [github.com/github/copilot-cli](https://github.com/github/copilot-cli) |
|
|
124
|
+
| Cline CLI | `cline` | [github.com/cline/cline](https://github.com/cline/cline) |
|
|
125
|
+
| OpenCode | `opencode` | [opencode.ai/docs](https://opencode.ai/docs) |
|
|
125
126
|
|
|
126
127
|
Each assistant has its own state detection strategy to properly track:
|
|
127
128
|
- **Idle**: Ready for new input
|
|
128
129
|
- **Busy**: Processing a request
|
|
129
130
|
- **Waiting**: Awaiting user confirmation
|
|
130
131
|
|
|
131
|
-
See [Gemini Support Documentation](docs/gemini-support.md) for
|
|
132
|
+
See [Gemini Support Documentation](docs/gemini-support.md) for CCManager-specific configuration.
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
## Command Configuration
|
|
@@ -25,6 +25,16 @@ const createStrategyItems = () => {
|
|
|
25
25
|
};
|
|
26
26
|
// Type-safe strategy items that ensures all StateDetectionStrategy values are included
|
|
27
27
|
const ALL_STRATEGY_ITEMS = createStrategyItems();
|
|
28
|
+
// Default command mapping for each strategy
|
|
29
|
+
const DEFAULT_COMMANDS = {
|
|
30
|
+
claude: 'claude',
|
|
31
|
+
gemini: 'gemini',
|
|
32
|
+
codex: 'codex',
|
|
33
|
+
cursor: 'cursor',
|
|
34
|
+
'github-copilot': 'copilot',
|
|
35
|
+
cline: 'cline',
|
|
36
|
+
opencode: 'opencode',
|
|
37
|
+
};
|
|
28
38
|
const formatDetectionStrategy = (strategy) => {
|
|
29
39
|
const value = strategy || 'claude';
|
|
30
40
|
switch (value) {
|
|
@@ -57,7 +67,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
57
67
|
const [isSelectingStrategy, setIsSelectingStrategy] = useState(false);
|
|
58
68
|
const [isSelectingStrategyInAdd, setIsSelectingStrategyInAdd] = useState(false);
|
|
59
69
|
const [newPreset, setNewPreset] = useState({});
|
|
60
|
-
const [addStep, setAddStep] = useState('
|
|
70
|
+
const [addStep, setAddStep] = useState('detectionStrategy');
|
|
61
71
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
62
72
|
// Remove handleListNavigation as SelectInput handles navigation internally
|
|
63
73
|
// Remove handleListSelection as we now use handleSelectItem
|
|
@@ -142,19 +152,8 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
142
152
|
};
|
|
143
153
|
const handleAddPresetInput = (value) => {
|
|
144
154
|
switch (addStep) {
|
|
145
|
-
case 'name':
|
|
146
|
-
// Prevent using "Default" as a name to avoid confusion
|
|
147
|
-
if (value.trim().toLowerCase() === 'default') {
|
|
148
|
-
setErrorMessage('Cannot use "Default" as a preset name. Please choose a different name.');
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
setNewPreset({ ...newPreset, name: value });
|
|
152
|
-
setAddStep('command');
|
|
153
|
-
setInputValue('');
|
|
154
|
-
setErrorMessage(null);
|
|
155
|
-
break;
|
|
156
155
|
case 'command':
|
|
157
|
-
setNewPreset({ ...newPreset, command: value
|
|
156
|
+
setNewPreset({ ...newPreset, command: value });
|
|
158
157
|
setAddStep('args');
|
|
159
158
|
setInputValue('');
|
|
160
159
|
break;
|
|
@@ -170,8 +169,38 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
170
169
|
? value.trim().split(/\s+/)
|
|
171
170
|
: undefined;
|
|
172
171
|
setNewPreset({ ...newPreset, fallbackArgs });
|
|
172
|
+
setAddStep('name');
|
|
173
|
+
setInputValue('');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'name': {
|
|
177
|
+
if (!value.trim()) {
|
|
178
|
+
setErrorMessage('Preset name cannot be empty. Please enter a name.');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (value.trim().toLowerCase() === 'default') {
|
|
182
|
+
setErrorMessage('Cannot use "Default" as a preset name. Please choose a different name.');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const id = Date.now().toString();
|
|
186
|
+
const completePreset = {
|
|
187
|
+
id,
|
|
188
|
+
name: value,
|
|
189
|
+
command: newPreset.command || 'claude',
|
|
190
|
+
args: newPreset.args,
|
|
191
|
+
fallbackArgs: newPreset.fallbackArgs,
|
|
192
|
+
detectionStrategy: newPreset.detectionStrategy || 'claude',
|
|
193
|
+
};
|
|
194
|
+
const updatedPresets = [...presets, completePreset];
|
|
195
|
+
setPresets(updatedPresets);
|
|
196
|
+
configurationManager.addPreset(completePreset);
|
|
197
|
+
setViewMode('list');
|
|
198
|
+
setSelectedIndex(updatedPresets.length - 1);
|
|
199
|
+
setNewPreset({});
|
|
173
200
|
setAddStep('detectionStrategy');
|
|
174
|
-
|
|
201
|
+
setInputValue('');
|
|
202
|
+
setIsSelectingStrategyInAdd(false);
|
|
203
|
+
setErrorMessage(null);
|
|
175
204
|
break;
|
|
176
205
|
}
|
|
177
206
|
}
|
|
@@ -188,25 +217,16 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
188
217
|
setIsSelectingStrategy(false);
|
|
189
218
|
};
|
|
190
219
|
const handleAddStrategySelect = (item) => {
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const updatedPresets = [...presets, completePreset];
|
|
201
|
-
setPresets(updatedPresets);
|
|
202
|
-
configurationManager.addPreset(completePreset);
|
|
203
|
-
setViewMode('list');
|
|
204
|
-
setSelectedIndex(updatedPresets.length - 1);
|
|
205
|
-
setNewPreset({});
|
|
206
|
-
setAddStep('name');
|
|
207
|
-
setInputValue('');
|
|
220
|
+
const strategy = item.value;
|
|
221
|
+
const defaultCommand = DEFAULT_COMMANDS[strategy];
|
|
222
|
+
setNewPreset({
|
|
223
|
+
...newPreset,
|
|
224
|
+
detectionStrategy: strategy,
|
|
225
|
+
command: defaultCommand,
|
|
226
|
+
});
|
|
227
|
+
setAddStep('command');
|
|
228
|
+
setInputValue(defaultCommand);
|
|
208
229
|
setIsSelectingStrategyInAdd(false);
|
|
209
|
-
setErrorMessage(null);
|
|
210
230
|
};
|
|
211
231
|
const handleDeleteConfirm = () => {
|
|
212
232
|
if (selectedIndex === 0) {
|
|
@@ -239,7 +259,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
239
259
|
else if (isSelectingStrategyInAdd) {
|
|
240
260
|
setIsSelectingStrategyInAdd(false);
|
|
241
261
|
setViewMode('list');
|
|
242
|
-
setAddStep('
|
|
262
|
+
setAddStep('detectionStrategy');
|
|
243
263
|
setNewPreset({});
|
|
244
264
|
}
|
|
245
265
|
else if (editField) {
|
|
@@ -338,6 +358,8 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
338
358
|
React.createElement(Text, { bold: true, color: "green" }, "Add New Preset - Detection Strategy")),
|
|
339
359
|
React.createElement(Box, { marginBottom: 1 },
|
|
340
360
|
React.createElement(Text, null, "Choose the state detection strategy for this preset:")),
|
|
361
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
362
|
+
React.createElement(Text, { dimColor: true }, "The command will be auto-set based on the strategy (can be changed later)")),
|
|
341
363
|
React.createElement(SelectInput, { items: strategyItems, onSelect: handleAddStrategySelect, initialIndex: 0 }),
|
|
342
364
|
React.createElement(Box, { marginTop: 1 },
|
|
343
365
|
React.createElement(Text, { dimColor: true },
|
|
@@ -347,23 +369,26 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
347
369
|
" to cancel"))));
|
|
348
370
|
}
|
|
349
371
|
const titles = {
|
|
350
|
-
|
|
351
|
-
command: 'Enter command (
|
|
372
|
+
detectionStrategy: 'Select detection strategy:',
|
|
373
|
+
command: 'Enter command (default set by strategy, can be modified):',
|
|
352
374
|
args: 'Enter command arguments (space-separated):',
|
|
353
375
|
fallbackArgs: 'Enter fallback arguments (space-separated):',
|
|
376
|
+
name: 'Enter preset name (freely customizable):',
|
|
354
377
|
};
|
|
355
378
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
356
379
|
React.createElement(Box, { marginBottom: 1 },
|
|
357
380
|
React.createElement(Text, { bold: true, color: "green" }, "Add New Preset")),
|
|
358
381
|
React.createElement(Box, { marginBottom: 1 },
|
|
359
382
|
React.createElement(Text, null, titles[addStep])),
|
|
383
|
+
addStep === 'command' && (React.createElement(Box, { marginBottom: 1 },
|
|
384
|
+
React.createElement(Text, { dimColor: true }, "Auto-filled from your strategy selection. You can change this if needed."))),
|
|
360
385
|
errorMessage && (React.createElement(Box, { marginBottom: 1 },
|
|
361
386
|
React.createElement(Text, { color: "red" }, errorMessage))),
|
|
362
387
|
React.createElement(Box, null,
|
|
363
|
-
React.createElement(TextInputWrapper, { value: inputValue, onChange: setInputValue, onSubmit: handleAddPresetInput, placeholder: addStep === '
|
|
364
|
-
? 'e.g.,
|
|
365
|
-
: addStep === '
|
|
366
|
-
? 'e.g.,
|
|
388
|
+
React.createElement(TextInputWrapper, { value: inputValue, onChange: setInputValue, onSubmit: handleAddPresetInput, placeholder: addStep === 'name'
|
|
389
|
+
? 'e.g., Development'
|
|
390
|
+
: addStep === 'args' || addStep === 'fallbackArgs'
|
|
391
|
+
? 'e.g., --resume or leave empty'
|
|
367
392
|
: '' })),
|
|
368
393
|
React.createElement(Box, { marginTop: 1 },
|
|
369
394
|
React.createElement(Text, { dimColor: true },
|
|
@@ -494,7 +519,8 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
494
519
|
// Add New Preset
|
|
495
520
|
setViewMode('add');
|
|
496
521
|
setNewPreset({});
|
|
497
|
-
setAddStep('
|
|
522
|
+
setAddStep('detectionStrategy');
|
|
523
|
+
setIsSelectingStrategyInAdd(true);
|
|
498
524
|
setInputValue('');
|
|
499
525
|
}
|
|
500
526
|
else if (item.value === 'exit') {
|
|
@@ -1,68 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useStdout } from 'ink';
|
|
3
3
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
4
4
|
const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
5
5
|
const { stdout } = useStdout();
|
|
6
6
|
const [isExiting, setIsExiting] = useState(false);
|
|
7
|
-
const deriveStatus = (currentSession) => {
|
|
8
|
-
const stateData = currentSession.stateMutex.getSnapshot();
|
|
9
|
-
// Always prioritize showing the manual approval notice when verification failed
|
|
10
|
-
if (stateData.autoApprovalFailed) {
|
|
11
|
-
const reason = stateData.autoApprovalReason
|
|
12
|
-
? ` Reason: ${stateData.autoApprovalReason}.`
|
|
13
|
-
: '';
|
|
14
|
-
return {
|
|
15
|
-
message: `Auto-approval failed.${reason} Manual approval required—respond to the prompt.`,
|
|
16
|
-
variant: 'error',
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
if (stateData.state === 'pending_auto_approval') {
|
|
20
|
-
return {
|
|
21
|
-
message: 'Auto-approval pending... verifying permissions (press any key to cancel)',
|
|
22
|
-
variant: 'pending',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return { message: null, variant: null };
|
|
26
|
-
};
|
|
27
|
-
const initialStatus = deriveStatus(session);
|
|
28
|
-
const [statusMessage, setStatusMessage] = useState(initialStatus.message);
|
|
29
|
-
const [statusVariant, setStatusVariant] = useState(initialStatus.variant);
|
|
30
|
-
const [columns, setColumns] = useState(() => stdout?.columns ?? process.stdout.columns ?? 80);
|
|
31
|
-
const { statusLineText, backgroundColor, textColor } = useMemo(() => {
|
|
32
|
-
if (!statusMessage || !statusVariant) {
|
|
33
|
-
return {
|
|
34
|
-
statusLineText: null,
|
|
35
|
-
backgroundColor: undefined,
|
|
36
|
-
textColor: undefined,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const maxContentWidth = Math.max(columns - 4, 0);
|
|
40
|
-
const prefix = statusVariant === 'error'
|
|
41
|
-
? '[AUTO-APPROVAL REQUIRED]'
|
|
42
|
-
: '[AUTO-APPROVAL]';
|
|
43
|
-
const prefixed = `${prefix} ${statusMessage}`;
|
|
44
|
-
const trimmed = prefixed.length > maxContentWidth
|
|
45
|
-
? prefixed.slice(0, maxContentWidth)
|
|
46
|
-
: prefixed;
|
|
47
|
-
return {
|
|
48
|
-
statusLineText: ` ${trimmed}`.padEnd(columns, ' '),
|
|
49
|
-
backgroundColor: statusVariant === 'error' ? '#d90429' : '#ffd166',
|
|
50
|
-
textColor: statusVariant === 'error' ? 'white' : '#1c1c1c',
|
|
51
|
-
};
|
|
52
|
-
}, [columns, statusMessage, statusVariant]);
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
const handleSessionStateChange = (updatedSession) => {
|
|
55
|
-
if (updatedSession.id !== session.id)
|
|
56
|
-
return;
|
|
57
|
-
const { message, variant } = deriveStatus(updatedSession);
|
|
58
|
-
setStatusMessage(message);
|
|
59
|
-
setStatusVariant(variant);
|
|
60
|
-
};
|
|
61
|
-
sessionManager.on('sessionStateChanged', handleSessionStateChange);
|
|
62
|
-
return () => {
|
|
63
|
-
sessionManager.off('sessionStateChanged', handleSessionStateChange);
|
|
64
|
-
};
|
|
65
|
-
}, [session.id, sessionManager]);
|
|
66
7
|
const stripOscColorSequences = (input) => {
|
|
67
8
|
// Remove default foreground/background color OSC sequences that Codex emits
|
|
68
9
|
// These sequences leak as literal text when replaying buffered output
|
|
@@ -168,7 +109,6 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
168
109
|
const handleSessionExit = (exitedSession) => {
|
|
169
110
|
if (exitedSession.id === session.id) {
|
|
170
111
|
setIsExiting(true);
|
|
171
|
-
setStatusMessage(null);
|
|
172
112
|
// Don't call onReturnToMenu here - App component handles it
|
|
173
113
|
}
|
|
174
114
|
};
|
|
@@ -178,7 +118,6 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
178
118
|
const handleResize = () => {
|
|
179
119
|
const cols = process.stdout.columns || 80;
|
|
180
120
|
const rows = process.stdout.rows || 24;
|
|
181
|
-
setColumns(cols);
|
|
182
121
|
session.process.resize(cols, rows);
|
|
183
122
|
// Also resize the virtual terminal
|
|
184
123
|
if (session.terminal) {
|
|
@@ -244,7 +183,6 @@ const Session = ({ session, sessionManager, onReturnToMenu, }) => {
|
|
|
244
183
|
stdout.off('resize', handleResize);
|
|
245
184
|
};
|
|
246
185
|
}, [session, sessionManager, stdout, onReturnToMenu, isExiting]);
|
|
247
|
-
return
|
|
248
|
-
React.createElement(Text, { backgroundColor: backgroundColor, color: textColor, bold: true }, statusLineText))) : null;
|
|
186
|
+
return null;
|
|
249
187
|
};
|
|
250
188
|
export default Session;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
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": "3.3.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "3.3.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "3.3.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "3.3.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "3.3.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "3.3.2",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.3.2",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.3.2",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.3.2",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.3.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|
|
@@ -68,14 +68,14 @@
|
|
|
68
68
|
},
|
|
69
69
|
"prettier": "@vdemedes/prettier-config",
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@xterm/headless": "^
|
|
71
|
+
"@xterm/headless": "^6.0.0",
|
|
72
72
|
"effect": "^3.18.2",
|
|
73
73
|
"ink": "5.2.1",
|
|
74
74
|
"ink-select-input": "^6.0.0",
|
|
75
75
|
"ink-text-input": "^6.0.0",
|
|
76
76
|
"meow": "^11.0.0",
|
|
77
77
|
"react": "18.3.1",
|
|
78
|
-
"react-devtools-core": "^
|
|
78
|
+
"react-devtools-core": "^7.0.1",
|
|
79
79
|
"react-dom": "18.3.1",
|
|
80
80
|
"strip-ansi": "^7.1.0"
|
|
81
81
|
}
|