ccmanager 3.12.1 → 3.12.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.
|
@@ -7,11 +7,12 @@ const PresetSelector = ({ onSelect, onCancel, }) => {
|
|
|
7
7
|
const presetsConfig = configReader.getCommandPresets();
|
|
8
8
|
const [presets] = useState(presetsConfig.presets);
|
|
9
9
|
const defaultPresetId = presetsConfig.defaultPresetId;
|
|
10
|
-
const selectItems = presets.map(preset => {
|
|
10
|
+
const selectItems = presets.map((preset, index) => {
|
|
11
11
|
const isDefault = preset.id === defaultPresetId;
|
|
12
12
|
const args = preset.args?.join(' ') || '';
|
|
13
13
|
const fallback = preset.fallbackArgs?.join(' ') || '';
|
|
14
|
-
|
|
14
|
+
const numberPrefix = index < 9 ? `[${index + 1}] ` : '';
|
|
15
|
+
let label = numberPrefix + preset.name;
|
|
15
16
|
if (isDefault)
|
|
16
17
|
label += ' (default)';
|
|
17
18
|
label += `\n Command: ${preset.command}`;
|
|
@@ -39,8 +40,17 @@ const PresetSelector = ({ onSelect, onCancel, }) => {
|
|
|
39
40
|
useInput((input, key) => {
|
|
40
41
|
if (key.escape) {
|
|
41
42
|
onCancel();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Number keys 1-9: immediate launch
|
|
46
|
+
if (/^[1-9]$/.test(input)) {
|
|
47
|
+
const idx = parseInt(input) - 1;
|
|
48
|
+
if (idx < presets.length && presets[idx]) {
|
|
49
|
+
onSelect(presets[idx].id);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
42
52
|
}
|
|
43
53
|
});
|
|
44
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Select Command Preset" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a preset to start the session with" }) }), _jsx(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: initialIndex >= 0 ? initialIndex : 0 }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "
|
|
54
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Select Command Preset" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a preset to start the session with" }) }), _jsx(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: initialIndex >= 0 ? initialIndex : 0 }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 Navigate 1-9 Quick Select Enter Select ESC Cancel" }) })] }));
|
|
45
55
|
};
|
|
46
56
|
export default PresetSelector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from 'ink-testing-library';
|
|
3
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
// Hoist mocks to avoid top-level variable access in vi.mock factories
|
|
5
|
+
const { capturedHandlers } = vi.hoisted(() => {
|
|
6
|
+
const capturedHandlers = {
|
|
7
|
+
inputHandler: null,
|
|
8
|
+
};
|
|
9
|
+
return { capturedHandlers };
|
|
10
|
+
});
|
|
11
|
+
// Mock ink to avoid stdin issues and capture useInput callbacks
|
|
12
|
+
vi.mock('ink', async () => {
|
|
13
|
+
const actual = await vi.importActual('ink');
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
16
|
+
useInput: vi.fn((handler) => {
|
|
17
|
+
capturedHandlers.inputHandler = handler;
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
// Mock SelectInput
|
|
22
|
+
vi.mock('ink-select-input', async () => {
|
|
23
|
+
const React = await vi.importActual('react');
|
|
24
|
+
const { Text, Box } = await vi.importActual('ink');
|
|
25
|
+
return {
|
|
26
|
+
default: ({ items, onSelect: _onSelect, initialIndex = 0, }) => {
|
|
27
|
+
return React.createElement(Box, { flexDirection: 'column' }, items.map((item, index) => React.createElement(Text, { key: index }, `${index === initialIndex ? '❯ ' : ' '}${item.label}`)));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
// Mock configReader
|
|
32
|
+
vi.mock('../services/config/configReader.js', () => ({
|
|
33
|
+
configReader: {
|
|
34
|
+
getCommandPresets: vi.fn().mockReturnValue({
|
|
35
|
+
presets: [
|
|
36
|
+
{ id: 'preset-1', name: 'Claude', command: 'claude' },
|
|
37
|
+
{ id: 'preset-2', name: 'Gemini', command: 'gemini' },
|
|
38
|
+
{ id: 'preset-3', name: 'Cursor', command: 'cursor' },
|
|
39
|
+
],
|
|
40
|
+
defaultPresetId: 'preset-1',
|
|
41
|
+
selectPresetOnStart: true,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
import PresetSelector from './PresetSelector.js';
|
|
46
|
+
const makeKey = (overrides = {}) => ({
|
|
47
|
+
upArrow: false,
|
|
48
|
+
downArrow: false,
|
|
49
|
+
leftArrow: false,
|
|
50
|
+
rightArrow: false,
|
|
51
|
+
pageDown: false,
|
|
52
|
+
pageUp: false,
|
|
53
|
+
home: false,
|
|
54
|
+
end: false,
|
|
55
|
+
return: false,
|
|
56
|
+
escape: false,
|
|
57
|
+
ctrl: false,
|
|
58
|
+
shift: false,
|
|
59
|
+
tab: false,
|
|
60
|
+
backspace: false,
|
|
61
|
+
delete: false,
|
|
62
|
+
meta: false,
|
|
63
|
+
...overrides,
|
|
64
|
+
});
|
|
65
|
+
describe('PresetSelector component', () => {
|
|
66
|
+
let onSelect;
|
|
67
|
+
let onCancel;
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
onSelect = vi.fn();
|
|
70
|
+
onCancel = vi.fn();
|
|
71
|
+
capturedHandlers.inputHandler = null;
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
vi.clearAllMocks();
|
|
75
|
+
});
|
|
76
|
+
it('renders preset list with number prefixes and default label', () => {
|
|
77
|
+
const { lastFrame } = render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
78
|
+
const output = lastFrame();
|
|
79
|
+
expect(output).toContain('[1]');
|
|
80
|
+
expect(output).toContain('[2]');
|
|
81
|
+
expect(output).toContain('[3]');
|
|
82
|
+
expect(output).toContain('(default)');
|
|
83
|
+
expect(output).toContain('← Cancel');
|
|
84
|
+
});
|
|
85
|
+
it('pressing 1 calls onSelect with first preset id immediately', () => {
|
|
86
|
+
render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
87
|
+
expect(capturedHandlers.inputHandler).not.toBeNull();
|
|
88
|
+
capturedHandlers.inputHandler('1', makeKey());
|
|
89
|
+
expect(onSelect).toHaveBeenCalledWith('preset-1');
|
|
90
|
+
expect(onCancel).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
it('pressing 2 calls onSelect with second preset id immediately', () => {
|
|
93
|
+
render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
94
|
+
capturedHandlers.inputHandler('2', makeKey());
|
|
95
|
+
expect(onSelect).toHaveBeenCalledWith('preset-2');
|
|
96
|
+
});
|
|
97
|
+
it('pressing 3 calls onSelect with third preset id immediately', () => {
|
|
98
|
+
render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
99
|
+
capturedHandlers.inputHandler('3', makeKey());
|
|
100
|
+
expect(onSelect).toHaveBeenCalledWith('preset-3');
|
|
101
|
+
});
|
|
102
|
+
it('pressing a number beyond preset count does nothing', () => {
|
|
103
|
+
render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
104
|
+
capturedHandlers.inputHandler('9', makeKey());
|
|
105
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
106
|
+
expect(onCancel).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
it('pressing ESC calls onCancel', () => {
|
|
109
|
+
render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
110
|
+
capturedHandlers.inputHandler('', makeKey({ escape: true }));
|
|
111
|
+
expect(onCancel).toHaveBeenCalled();
|
|
112
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
it('displays title and subtitle', () => {
|
|
115
|
+
const { lastFrame } = render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
|
|
116
|
+
const output = lastFrame();
|
|
117
|
+
expect(output).toContain('Select Command Preset');
|
|
118
|
+
expect(output).toContain('Choose a preset to start the session with');
|
|
119
|
+
});
|
|
120
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.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.12.
|
|
45
|
-
"@kodaikabasawa/ccmanager-darwin-x64": "3.12.
|
|
46
|
-
"@kodaikabasawa/ccmanager-linux-arm64": "3.12.
|
|
47
|
-
"@kodaikabasawa/ccmanager-linux-x64": "3.12.
|
|
48
|
-
"@kodaikabasawa/ccmanager-win32-x64": "3.12.
|
|
44
|
+
"@kodaikabasawa/ccmanager-darwin-arm64": "3.12.2",
|
|
45
|
+
"@kodaikabasawa/ccmanager-darwin-x64": "3.12.2",
|
|
46
|
+
"@kodaikabasawa/ccmanager-linux-arm64": "3.12.2",
|
|
47
|
+
"@kodaikabasawa/ccmanager-linux-x64": "3.12.2",
|
|
48
|
+
"@kodaikabasawa/ccmanager-win32-x64": "3.12.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/js": "^9.28.0",
|